diff options
Diffstat (limited to 'chalk/core')
154 files changed, 30751 insertions, 0 deletions
diff --git a/chalk/core/Makefile.am b/chalk/core/Makefile.am new file mode 100644 index 00000000..30b1eb07 --- /dev/null +++ b/chalk/core/Makefile.am @@ -0,0 +1,59 @@ +# all_includes must remain last! +INCLUDES = \ + -I$(srcdir)/../sdk \ + -I$(srcdir)/tiles \ + -I$(srcdir)/../chalkcolor \ + $(KOFFICE_INCLUDES) \ + $(KOPAINTER_INCLUDES) \ + $(OPENEXR_CFLAGS) \ + $(all_includes) + +#CXXFLAGS = -shared -fPIC + +lib_LTLIBRARIES = libchalkimage.la + +libchalkimage_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 + +libchalkimage_la_LDFLAGS = -version-info 1:0:0 -no-undefined $(all_libraries) +libchalkimage_la_LIBADD = ../sdk/libchalksdk.la ../chalkcolor/libchalkcolor.la tiles/libchalktile.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) + +libchalkimage_la_METASOURCES = AUTO + +KDE_OPTIONS = nofinal + diff --git a/chalk/core/createdcop.py b/chalk/core/createdcop.py new file mode 100755 index 00000000..bac48f33 --- /dev/null +++ b/chalk/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 * tqparent ); +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 * tqparent ) + : DCOPObject() +{ + m_parent = tqparent; +} + +""" + +def parseHeader(headerfile, classname): + # parse the source class header to get a list of functions we're going to wrap + functions = [] + if (headerfile.tqfind("private:") > -1): + lines = headerfile[headerfile.tqfind(classname):headerfile.tqfind("private")].splitlines() + else: + lines = headerfile[headerfile.tqfind(classname):headerfile.tqfind("#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 Q_SLOTS:") or + line.tqfind("~") != -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.tqfind("{") > -1): + function = line[:line.tqfind("{")] + if function.tqfind("}") > -1: + function += line[line.tqfind("}") + 1:] + complete = 1 + else: + i += 1 + # search for the missing } on the next lines + while i < len(lines): + if (lines[i].tqfind("}") > -1): + function += lines[i][lines[i].tqfind("}") + 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/chalk/core/kis_adjustment_layer.cc b/chalk/core/kis_adjustment_layer.cc new file mode 100644 index 00000000..69e8b939 --- /dev/null +++ b/chalk/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 <tqimage.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 TQString &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, TQT_SIGNAL(sigSelectionChanged(KisImageSP)), + this, TQT_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(), TQT_SIGNAL(sigSelectionChanged(KisImageSP)), + this, TQT_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(TQt::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(); + + TQRect bounds = extent(); + bounds |= image()->bounds(); + gc.fillRect(bounds, KisColor(TQt::white, cs), MIN_SELECTED); + gc.end(); +} + + +TQ_INT32 KisAdjustmentLayer::x() const +{ + if (m_selection) + return m_selection->getX(); + else + return 0; +} + +void KisAdjustmentLayer::setX(TQ_INT32 x) +{ + if (m_selection) { + m_selection->setX(x); + resetCache(); + } + +} + +TQ_INT32 KisAdjustmentLayer::y() const +{ + if (m_selection) + return m_selection->getY(); + else + return 0; +} + +void KisAdjustmentLayer::setY(TQ_INT32 y) +{ + if (m_selection) { + m_selection->setY(y); + resetCache(); + } +} + +TQRect KisAdjustmentLayer::extent() const +{ + if (m_selection) + return m_selection->selectedRect(); + else if (image()) + return image()->bounds(); + else + return TQRect(); +} + +TQRect KisAdjustmentLayer::exactBounds() const +{ + if (m_selection) + return m_selection->selectedRect(); + else if (image()) + return image()->bounds(); + else + return TQRect(); +} + +bool KisAdjustmentLayer::accept(KisLayerVisitor & v) +{ + return v.visit( this ); +} + +void KisAdjustmentLayer::paintSelection(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + if (showSelection() && selection()) + selection()->paintSelection(img, x, y, w, h); +} + +void KisAdjustmentLayer::paintSelection(TQImage &img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize) +{ + if (showSelection() && selection()) + selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); +} + +TQImage KisAdjustmentLayer::createThumbnail(TQ_INT32 w, TQ_INT32 h) +{ + if (!selection()) + return TQImage(); + + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const TQRect e = extent(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = TQ_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = TQ_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = TQ_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = TQ_INT32(double(srcw) / srch * h); + + TQColor c; + TQ_UINT8 opacity; + TQImage img(w,h,32); + + for (TQ_INT32 y=0; y < h; ++y) { + TQ_INT32 iY = (y * srch ) / h; + for (TQ_INT32 x=0; x < w; ++x) { + TQ_INT32 iX = (x * srcw ) / w; + m_selection->pixel(iX, iY, &c, &opacity); + img.setPixel(x, y, tqRgb(opacity, opacity, opacity)); + } + } + + return img; +} + +void KisAdjustmentLayer::slotSelectionChanged(KisImageSP image) { + image->setModified(); +} + +#include "kis_adjustment_layer.moc" diff --git a/chalk/core/kis_adjustment_layer.h b/chalk/core/kis_adjustment_layer.h new file mode 100644 index 00000000..b8b76437 --- /dev/null +++ b/chalk/core/kis_adjustment_layer.h @@ -0,0 +1,106 @@ +/* + * 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 <tqobject.h> +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_composite_op.h" +#include <koffice_export.h> + +class KNamedCommand; +class TQPainter; +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 + TQ_OBJECT + +public: + /** + * Create a new adjustment layer with the given configuration and selection. + * Note that the selection will be _copied_. + */ + KisAdjustmentLayer(KisImageSP img, const TQString &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(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + virtual void paintSelection(TQImage &img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize); +public: + + virtual TQ_INT32 x() const; + virtual void setX(TQ_INT32); + + virtual TQ_INT32 y() const; + virtual void setY(TQ_INT32); + + /// Returns an approximation of where the bounds on actual data are in this layer + virtual TQRect extent() const; + + /// Returns the exact bounds of where the actual data resides in this layer + virtual TQRect 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 TQImage createThumbnail(TQ_INT32 w, TQ_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/chalk/core/kis_alpha_mask.cc b/chalk/core/kis_alpha_mask.cc new file mode 100644 index 00000000..1b40e806 --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqvaluevector.h> + +#include <kdebug.h> + +#include "kis_global.h" +#include "kis_alpha_mask.h" + +KisAlphaMask::KisAlphaMask(const TQImage& img, bool hasColor) +{ + m_width = img.width(); + m_height = img.height(); + + if (hasColor) { + copyAlpha(img); + } + else { + computeAlpha(img); + } +} + +KisAlphaMask::KisAlphaMask(const TQImage& img) +{ + m_width = img.width(); + m_height = img.height(); + + if (!img.allGray()) { + copyAlpha(img); + } + else { + computeAlpha(img); + } +} + +KisAlphaMask::KisAlphaMask(TQ_INT32 width, TQ_INT32 height) +{ + m_width = width; + m_height = height; + + m_data.resize(width * height, OPACITY_TRANSPARENT); +} + +KisAlphaMask::~KisAlphaMask() +{ +} + +TQ_INT32 KisAlphaMask::width() const +{ + return m_width; +} + +TQ_INT32 KisAlphaMask::height() const +{ + return m_height; +} + +void KisAlphaMask::setAlphaAt(TQ_INT32 x, TQ_INT32 y, TQ_UINT8 alpha) +{ + if (y >= 0 && y < m_height && x >= 0 && x < m_width) { + m_data[(y * m_width) + x] = alpha; + } +} + +void KisAlphaMask::copyAlpha(const TQImage& img) +{ + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + TQRgb c = img.pixel(x,y); + TQ_UINT8 a = (tqGray(c) * tqAlpha(c)) / 255; + m_data.push_back(a); + + } + } +} + +void KisAlphaMask::computeAlpha(const TQImage& 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 - tqRed(img.pixel(x, y))); + } + } +} + +KisAlphaMaskSP KisAlphaMask::interpolate(KisAlphaMaskSP tqmask1, KisAlphaMaskSP tqmask2, double t) +{ + Q_ASSERT((tqmask1->width() == tqmask2->width()) && (tqmask1->height() == tqmask2->height())); + Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); + + int width = tqmask1->width(); + int height = tqmask1->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++) { + TQ_UINT8 d = static_cast<TQ_UINT8>((1 - t) * tqmask1->alphaAt(x, y) + t * tqmask2->alphaAt(x, y)); + outputMask->setAlphaAt(x, y, d); + } + } + + return outputMask; +} + + diff --git a/chalk/core/kis_alpha_mask.h b/chalk/core/kis_alpha_mask.h new file mode 100644 index 00000000..4665857e --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqvaluevector.h> + +#include <ksharedptr.h> + +#include "kis_global.h" +#include "kis_types.h" + +/** + * KisAlphaMask is intended to create alpha values from a TQImage for use + * in brush creation. It is not a generic alpha tqmask that can be used with + * KisPaintDevices: use a KisSelection for that. + */ +class KisAlphaMask : public KShared { + + public: + /** + Create an alpha tqmask based on the specified TQImage. If the image is + not a grayscale, the tqmask value is calculated from the effective grey + level and alpha value. + */ + KisAlphaMask(const TQImage& img); + + /** + As above except quicker as the image does not need to be scanned + to see if it has any colour pixels. + */ + KisAlphaMask(const TQImage& img, bool hasColor); + + /** + Create a transparent tqmask. + */ + KisAlphaMask(TQ_INT32 width, TQ_INT32 height); + + virtual ~KisAlphaMask(); + + /** + @return the number of alpha values in a scanline. + */ + TQ_INT32 height() const; + + /** + @return the number of lines in the tqmask. + */ + TQ_INT32 width() const; + + /** + @return the alpha value at the specified position. + + Returns TQ_UINT8 OPACITY_TRANSPARENT if the value is + outside the bounds of the tqmask. + + XXX: this is, of course, not the best way of tqmasking. + Better would be to let KisAlphaMask fill a chunk of memory + with the alpha values at the right position, something like + void applyMask(TQ_UINT8 *pixeldata, TQ_INT32 pixelWidth, + TQ_INT32 alphaPos). That would be fastest, or we could + provide an iterator over the tqmask, that would be nice, too. + */ + inline TQ_UINT8 alphaAt(TQ_INT32 x, TQ_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(TQ_INT32 x, TQ_INT32 y, TQ_UINT8 alpha); + + // Create a new tqmask by interpolating between tqmask1 and tqmask2 as t + // goes from 0 to 1. + static KisAlphaMaskSP interpolate(KisAlphaMaskSP tqmask1, KisAlphaMaskSP tqmask2, double t); + +private: + void computeAlpha(const TQImage& img); + void copyAlpha(const TQImage& img); + + TQValueVector<TQ_UINT8> m_data; + TQ_INT32 m_width; + TQ_INT32 m_height; +}; + +#endif // KIS_ALPHA_MASK_ + diff --git a/chalk/core/kis_autobrush_resource.cc b/chalk/core/kis_autobrush_resource.cc new file mode 100644 index 00000000..9a09fe78 --- /dev/null +++ b/chalk/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( TQImage* img) +{ + img->create(m_w, m_h, 32); + for(int j = 0; j < m_h; j++) + { + for(int i = 0; i < m_w; i++) + { + TQ_INT8 v = valueAt(i,j); + img->setPixel( i, j, tqRgb(v,v,v)); + } + } +} + +KisAutobrushCircleShape::KisAutobrushCircleShape(TQ_INT32 w, TQ_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)) +{ +} +TQ_INT8 KisAutobrushCircleShape::valueAt(TQ_INT32 x, TQ_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(TQ_INT32 w, TQ_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) +{ +} +TQ_INT8 KisAutobrushRectShape::valueAt(TQ_INT32 x, TQ_INT32 y) +{ + double xr = TQABS(x - m_xcentre); + double yr = TQABS(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/chalk/core/kis_autobrush_resource.h b/chalk/core/kis_autobrush_resource.h new file mode 100644 index 00000000..44708efa --- /dev/null +++ b/chalk/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(TQ_INT32 w, TQ_INT32 h, double fh, double fv) : m_w(w), m_h(h), m_fh(fh), m_fv(fv) + { }; + void createBrush( TQImage* img); + protected: + virtual TQ_INT8 valueAt(TQ_INT32 x, TQ_INT32 y) =0; + TQ_INT32 m_w, m_h; + double m_fh, m_fv; +}; + +class KisAutobrushCircleShape : public KisAutobrushShape { + public: + KisAutobrushCircleShape(TQ_INT32 w, TQ_INT32 h, double fh, double fv); + public: + virtual TQ_INT8 valueAt(TQ_INT32 x, TQ_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(TQ_INT32 w, TQ_INT32 h, double fh, double fv); + protected: + virtual TQ_INT8 valueAt(TQ_INT32 x, TQ_INT32 y); + private: + double m_xcentre, m_ycentre, m_c; +}; + +class KisAutobrushResource : public KisBrush +{ + public: + KisAutobrushResource(TQImage& img) : KisBrush("") + { + setImage(img); + setBrushType(MASK); + }; + public: + virtual bool load() { return false; }; +}; +#endif // _KIS_AUTOBRUSH_RESOURCE_H_ diff --git a/chalk/core/kis_autogradient_resource.cc b/chalk/core/kis_autogradient_resource.cc new file mode 100644 index 00000000..671e8fc2 --- /dev/null +++ b/chalk/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, TQColor left, TQColor right ) +{ + pushSegment(new KisGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, Color( left, 1 ), Color( right, 1 ))); + +} + +const TQValueVector<double> KisAutogradientResource::getHandlePositions() const +{ + TQValueVector<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 TQValueVector<double> KisAutogradientResource::getMiddleHandlePositions() const +{ + TQValueVector<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) +{ + TQValueVector<KisGradientSegment*>::iterator it = tqFind( 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) +{ + TQValueVector<KisGradientSegment*>::iterator it = tqFind( 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); + TQValueVector<KisGradientSegment*>::iterator it = tqFind( 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); + TQValueVector<KisGradientSegment*>::iterator it = tqFind( 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; + TQValueVector<KisGradientSegment*>::iterator it = tqFind( 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/chalk/core/kis_autogradient_resource.h b/chalk/core/kis_autogradient_resource.h new file mode 100644 index 00000000..37546a02 --- /dev/null +++ b/chalk/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, TQColor left, TQColor right ); + + const TQValueVector<double> getHandlePositions() const; + const TQValueVector<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/chalk/core/kis_background.cc b/chalk/core/kis_background.cc new file mode 100644 index 00000000..6f881d33 --- /dev/null +++ b/chalk/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 = TQImage(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++) + { + TQ_UINT8 v = 128 + 63 * ((x / 16 + y / 16) % 2); + m_patternTile.setPixel(x, y, tqRgb(v, v, v)); + } + } +} + +KisBackground::~KisBackground() +{ +} + +const TQImage& KisBackground::patternTile() const +{ + return m_patternTile; +} + +void KisBackground::paintBackground(TQImage 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++) + { + TQRgb *imagePixelPtr = reinterpret_cast<TQRgb *>(image.scanLine(y)); + const TQRgb *patternScanLine = reinterpret_cast<const TQRgb *>(m_patternTile.scanLine(patternY)); + int patternX = patternLeftX; + + for (int x = 0; x < imageWidth; x++) + { + TQRgb imagePixel = *imagePixelPtr; + TQ_UINT8 imagePixelAlpha = tqAlpha(imagePixel); + + if (imagePixelAlpha != 255) { + + TQRgb patternPixel = patternScanLine[patternX]; + TQ_UINT8 imageRed = UINT8_BLEND(tqRed(imagePixel), tqRed(patternPixel), imagePixelAlpha); + TQ_UINT8 imageGreen = UINT8_BLEND(tqGreen(imagePixel), tqGreen(patternPixel), imagePixelAlpha); + TQ_UINT8 imageBlue = UINT8_BLEND(tqBlue(imagePixel), tqBlue(patternPixel), imagePixelAlpha); + + *imagePixelPtr = tqRgba(imageRed, imageGreen, imageBlue, 255); + } + + ++imagePixelPtr; + ++patternX; + + if (patternX == PATTERN_WIDTH) { + patternX = 0; + } + } + + ++patternY; + + if (patternY == PATTERN_HEIGHT) { + patternY = 0; + } + } +} + +void KisBackground::paintBackground(TQImage img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize) +{ + if (scaledImageRect.isEmpty() || scaledImageSize.isEmpty() || imageSize.isEmpty()) { + return; + } + + Q_ASSERT(img.size() == scaledImageRect.size()); + + if (img.size() != scaledImageRect.size()) { + return; + } + + TQ_INT32 imageWidth = imageSize.width(); + TQ_INT32 imageHeight = imageSize.height(); + + for (TQ_INT32 y = 0; y < scaledImageRect.height(); ++y) { + + TQ_INT32 scaledY = scaledImageRect.y() + y; + TQ_INT32 srcY = (scaledY * imageHeight) / scaledImageSize.height(); + TQ_INT32 patternY = srcY % PATTERN_HEIGHT; + + TQRgb *imagePixelPtr = reinterpret_cast<TQRgb *>(img.scanLine(y)); + const TQRgb *patternScanLine = reinterpret_cast<const TQRgb *>(m_patternTile.scanLine(patternY)); + + for (TQ_INT32 x = 0; x < scaledImageRect.width(); ++x) { + + TQRgb imagePixel = *imagePixelPtr; + TQ_UINT8 imagePixelAlpha = tqAlpha(imagePixel); + + if (imagePixelAlpha != 255) { + + TQ_INT32 scaledX = scaledImageRect.x() + x; + TQ_INT32 srcX = (scaledX * imageWidth) / scaledImageSize.width(); + TQ_INT32 patternX = srcX % PATTERN_WIDTH; + + TQRgb patternPixel = patternScanLine[patternX]; + TQ_UINT8 imageRed = UINT8_BLEND(tqRed(imagePixel), tqRed(patternPixel), imagePixelAlpha); + TQ_UINT8 imageGreen = UINT8_BLEND(tqGreen(imagePixel), tqGreen(patternPixel), imagePixelAlpha); + TQ_UINT8 imageBlue = UINT8_BLEND(tqBlue(imagePixel), tqBlue(patternPixel), imagePixelAlpha); + + *imagePixelPtr = tqRgba(imageRed, imageGreen, imageBlue, 255); + } + + ++imagePixelPtr; + } + } +} + + diff --git a/chalk/core/kis_background.h b/chalk/core/kis_background.h new file mode 100644 index 00000000..f5b52903 --- /dev/null +++ b/chalk/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 <tqimage.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(TQImage image, int leftX, int topY); + + void paintBackground(TQImage image, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize); + + // Returns the pattern tile. + const TQImage& patternTile() const; + +protected: + static const int PATTERN_WIDTH = 32; + static const int PATTERN_HEIGHT = 32; + + TQImage m_patternTile; +}; + +#endif // KIS_BACKGROUND_H_ + diff --git a/chalk/core/kis_basic_math_toolbox.cpp b/chalk/core/kis_basic_math_toolbox.cpp new file mode 100644 index 00000000..1b397ab5 --- /dev/null +++ b/chalk/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 TQRect& 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 TQRect& rect, KisWavelet* wav, KisWavelet* buff) +{ + if(buff == 0) + { + buff = initWavelet( dst, rect ); + } + + waveuntrans(wav, buff, 1 ); + transformFromFR(dst, wav, rect); +} diff --git a/chalk/core/kis_basic_math_toolbox.h b/chalk/core/kis_basic_math_toolbox.h new file mode 100644 index 00000000..e5fa66cd --- /dev/null +++ b/chalk/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 TQRect&, KisWavelet* buff = 0); + virtual void fastWaveletUntransformation(KisPaintDeviceSP dst, const TQRect&, 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/chalk/core/kis_boundary.cc b/chalk/core/kis_boundary.cc new file mode 100644 index 00000000..0c9681c2 --- /dev/null +++ b/chalk/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 <tqpixmap.h> +#include <tqpainter.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(TQ_UINT8 val) { + return val < m_fuzzyness; +} + +void KisBoundary::generateBoundary(int w, int h) { + if (!m_device) + return; + + KisColorSpace* cs = m_device->colorSpace(); + + //Qt::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(TQValueList<PointPair>()); + + while (!topIt.isDone()) { + darkTop = cs->getAlpha(topIt.rawData()); + darkBot = cs->getAlpha(botIt.rawData()); + if (darkTop != darkBot) { + // detected a change + m_horSegments.back().append(tqMakePair(KisPoint(botIt.x(), botIt.y()), 1)); + } + ++topIt; + ++botIt; + } + } + + //Qt::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(TQValueList<PointPair>()); + + while (!leftIt.isDone()) { + darkLeft = cs->getAlpha(leftIt.rawData()); + darkRight = cs->getAlpha(rightIt.rawData()); + if (darkLeft != darkRight) { + // detected a change + m_vertSegments.back().append(tqMakePair(KisPoint(rightIt.x(), rightIt.y()), 1)); + } + ++leftIt; + ++rightIt; + } + } +} + diff --git a/chalk/core/kis_boundary.h b/chalk/core/kis_boundary.h new file mode 100644 index 00000000..6c5ce8c6 --- /dev/null +++ b/chalk/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 <tqvaluelist.h> +#include <tqpair.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 TQPair<KisPoint, int> PointPair; // int->length + bool isDark(TQ_UINT8 val); + KisPaintDevice* m_device; + int m_fuzzyness; + + typedef TQValueList<PointPair> PointPairList; + typedef TQValueList< PointPairList > PointPairListList; + + PointPairListList m_horSegments; + PointPairListList m_vertSegments; + + friend class KisBoundaryPainter; +}; + +#endif // _KIS_BOUNDARY_H_ diff --git a/chalk/core/kis_brush.cc b/chalk/core/kis_brush.cc new file mode 100644 index 00000000..9720a3e1 --- /dev/null +++ b/chalk/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 <tqfile.h> +#include <tqimage.h> +#include <tqpoint.h> +#include <tqvaluevector.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 { + TQ_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ + TQ_UINT32 version; /* brush file version # */ + TQ_UINT32 width; /* width of brush */ + TQ_UINT32 height; /* height of brush */ + TQ_UINT32 bytes; /* depth of brush in bytes */ + }; + + /// All fields are in MSB on disk! + struct GimpBrushHeader { + TQ_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ + TQ_UINT32 version; /* brush file version # */ + TQ_UINT32 width; /* width of brush */ + TQ_UINT32 height; /* height of brush */ + TQ_UINT32 bytes; /* depth of brush in bytes */ + + /* The following are only defined in version 2 */ + TQ_UINT32 magic_number; /* GIMP brush magic number */ + TQ_UINT32 spacing; /* brush spacing as % of width & height, 0 - 1000 */ + }; + + // Needed, or the GIMP won't open it! + TQ_UINT32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0); +} + +#define DEFAULT_SPACING 0.25 +#define MAXIMUM_SCALE 2 + +KisBrush::KisBrush(const TQString& 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 TQString& filename, + const TQByteArray& data, + TQ_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(TQString("")) +{ + 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 TQImage& image, const TQString& name) + : super(TQString("")) +{ + 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) { + TQFile 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; + } + + TQString 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 = TQString::fromAscii(text, bh.header_size - sizeof(GimpBrushV1Header)); + } else { + // ### Version = 3->cinepaint; may be float16 data! + // Version >=2: UTF-8 encoding is used + name = TQString::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; + } + + TQ_INT32 k = bh.header_size; + + if (bh.bytes == 1) { + // Grayscale + + if (static_cast<TQ_UINT32>(k + bh.width * bh.height) > m_data.size()) { + return false; + } + + m_brushType = MASK; + m_hasColor = false; + + for (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++, k++) { + TQ_INT32 val = 255 - static_cast<uchar>(m_data[k]); + m_img.setPixel(x, y, tqRgb(val, val, val)); + } + } + } else if (bh.bytes == 4) { + // RGBA + + if (static_cast<TQ_UINT32>(k + (bh.width * bh.height * 4)) > m_data.size()) { + return false; + } + + m_brushType = IMAGE; + m_img.setAlphaBuffer(true); + m_hasColor = true; + + for (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++, k += 4) { + m_img.setPixel(x, y, tqRgba(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->convertToTQImage(0, x, y, w, h)); + setName(image->name()); + + m_brushType = IMAGE; + m_hasColor = true; + + return true; +} + +bool KisBrush::save() +{ + TQFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + bool ok = saveToDevice(TQT_TQIODEVICE(&file)); + file.close(); + return ok; +} + +bool KisBrush::saveToDevice(TQIODevice* dev) const +{ + GimpBrushHeader bh; + TQCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8 + char const* name = utf8Name.data(); + int nameLength = tqstrlen(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<TQ_UINT32>(spacing() * 100.0)); + + // Write header: first bh, then the name + TQByteArray 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 (TQ_INT32 y = 0; y < height(); y++) { + for (TQ_INT32 x = 0; x < width(); x++) { + TQRgb c = m_img.pixel(x, y); + bytes[k++] = static_cast<char>(255 - tqRed(c)); // red == blue == green + } + } + } else { + bytes.resize(width() * height() * 4); + for (TQ_INT32 y = 0; y < height(); y++) { + for (TQ_INT32 x = 0; x < width(); x++) { + // order for gimp brushes, v2 is: RGBA + TQRgb pixel = m_img.pixel(x,y); + bytes[k++] = static_cast<char>(tqRed(pixel)); + bytes[k++] = static_cast<char>(tqGreen(pixel)); + bytes[k++] = static_cast<char>(tqBlue(pixel)); + bytes[k++] = static_cast<char>(tqAlpha(pixel)); + } + } + } + + wrote = dev->writeBlock(bytes); + if (wrote == -1) + return false; + + return true; +} + +TQImage KisBrush::img() +{ + TQImage image = m_img; + + if (hasColor() && useColorAsMask()) { + image.detach(); + + for (int x = 0; x < image.width(); x++) { + for (int y = 0; y < image.height(); y++) { + TQRgb c = image.pixel(x, y); + int a = (tqGray(c) * tqAlpha(c)) / 255; + image.setPixel(x, y, tqRgba(a, 0, a, a)); + } + } + } + + return image; +} + +KisAlphaMaskSP KisBrush::tqmask(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 tqmasks. 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 tqmask, which is always 1x1. + double s = scale / aboveBrush->scale(); + outputMask = scaleSinglePixelMask(s, aboveBrush->tqmask()->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); + + TQImage outputImage; + + if (belowBrush != 0) { + // We're in between two brushes. Interpolate between them. + + TQImage scaledAboveImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY); + TQImage 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++) { + TQ_UINT8 * p = iter.rawData(); + + TQRgb pixel = outputImage.pixel(x, y); + int red = tqRed(pixel); + int green = tqGreen(pixel); + int blue = tqBlue(pixel); + int alpha = tqAlpha(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; + + TQImage 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 : TQImage(), 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; +} + +TQ_INT32 KisBrush::tqmaskWidth(const KisPaintInformation& info) const +{ + // Add one for sub-pixel shift + return static_cast<TQ_INT32>(ceil(width() * scaleForPressure(info.pressure)) + 1); +} + +TQ_INT32 KisBrush::tqmaskHeight(const KisPaintInformation& info) const +{ + // Add one for sub-pixel shift + return static_cast<TQ_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->tqmask(); + + // 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; + + TQ_UINT8 topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX, topY) : OPACITY_TRANSPARENT; + TQ_UINT8 bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX, topY + 1) : OPACITY_TRANSPARENT; + TQ_UINT8 topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX + 1, topY) : OPACITY_TRANSPARENT; + TQ_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<TQ_UINT8>(d)); + } + } + + return dstMask; +} + +TQImage 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; + + TQImage dstImage(dstWidth, dstHeight, 32); + dstImage.setAlphaBuffer(true); + + const TQImage 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; + + TQRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : tqRgba(0, 0, 0, 0); + TQRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : tqRgba(0, 0, 0, 0); + TQRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : tqRgba(0, 0, 0, 0); + TQRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : tqRgba(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 * tqRed(topLeft) + + a * (1 - b) * tqRed(bottomLeft) + + (1 - a) * b * tqRed(topRight) + + (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5); + int green = static_cast<int>(a * b * tqGreen(topLeft) + + a * (1 - b) * tqGreen(bottomLeft) + + (1 - a) * b * tqGreen(topRight) + + (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5); + int blue = static_cast<int>(a * b * tqBlue(topLeft) + + a * (1 - b) * tqBlue(bottomLeft) + + (1 - a) * b * tqBlue(topRight) + + (1 - a) * (1 - b) * tqBlue(bottomRight) + 0.5); + int alpha = static_cast<int>(a * b * tqAlpha(topLeft) + + a * (1 - b) * tqAlpha(bottomLeft) + + (1 - a) * b * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqAlpha(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, tqRgba(red, green, blue, alpha)); + } + } + + return dstImage; +} + +TQImage KisBrush::scaleImage(const TQImage& srcImage, int width, int height) +{ + TQImage scaledImage; + //TQString 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 = TQString("smoothScale_%1x%2.png").tqarg(width).tqarg(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; + + TQRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : tqRgba(0, 0, 0, 0); + TQRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : tqRgba(0, 0, 0, 0); + TQRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : tqRgba(0, 0, 0, 0); + TQRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : tqRgba(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 * tqRed(topLeft) * tqAlpha(topLeft) + + a * (1 - b) * tqRed(bottomLeft) * tqAlpha(bottomLeft) + + (1 - a) * b * tqRed(topRight) * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqRed(bottomRight) * tqAlpha(bottomRight) + 0.5); + green = static_cast<int>(a * b * tqGreen(topLeft) * tqAlpha(topLeft) + + a * (1 - b) * tqGreen(bottomLeft) * tqAlpha(bottomLeft) + + (1 - a) * b * tqGreen(topRight) * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqGreen(bottomRight) * tqAlpha(bottomRight) + 0.5); + blue = static_cast<int>(a * b * tqBlue(topLeft) * tqAlpha(topLeft) + + a * (1 - b) * tqBlue(bottomLeft) * tqAlpha(bottomLeft) + + (1 - a) * b * tqBlue(topRight) * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqBlue(bottomRight) * tqAlpha(bottomRight) + 0.5); + alpha = static_cast<int>(a * b * tqAlpha(topLeft) + + a * (1 - b) * tqAlpha(bottomLeft) + + (1 - a) * b * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqAlpha(bottomRight) + 0.5); + + if (alpha != 0) { + red /= alpha; + green /= alpha; + blue /= alpha; + } + } + else { + red = static_cast<int>(a * b * tqRed(topLeft) + + a * (1 - b) * tqRed(bottomLeft) + + (1 - a) * b * tqRed(topRight) + + (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5); + green = static_cast<int>(a * b * tqGreen(topLeft) + + a * (1 - b) * tqGreen(bottomLeft) + + (1 - a) * b * tqGreen(topRight) + + (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5); + blue = static_cast<int>(a * b * tqBlue(topLeft) + + a * (1 - b) * tqBlue(bottomLeft) + + (1 - a) * b * tqBlue(topRight) + + (1 - a) * (1 - b) * tqBlue(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, tqRgba(red, green, blue, alpha)); + } + } + + //filename = TQString("bilinear_%1x%2.png").tqarg(width).tqarg(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, TQ_UINT8 tqmaskValue, 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++) { + + TQ_UINT8 topLeft = (x > 0 && y > 0) ? tqmaskValue : OPACITY_TRANSPARENT; + TQ_UINT8 bottomLeft = (x > 0 && y < srcHeight) ? tqmaskValue : OPACITY_TRANSPARENT; + TQ_UINT8 topRight = (x < srcWidth && y > 0) ? tqmaskValue : OPACITY_TRANSPARENT; + TQ_UINT8 bottomRight = (x < srcWidth && y < srcHeight) ? tqmaskValue : 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<TQ_UINT8>(d)); + } + } + + return outputMask; +} + +TQImage KisBrush::scaleSinglePixelImage(double scale, TQRgb pixel, double subPixelX, double subPixelY) +{ + int srcWidth = 1; + int srcHeight = 1; + int dstWidth = 2; + int dstHeight = 2; + + TQImage 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++) { + + TQRgb topLeft = (x > 0 && y > 0) ? pixel : tqRgba(0, 0, 0, 0); + TQRgb bottomLeft = (x > 0 && y < srcHeight) ? pixel : tqRgba(0, 0, 0, 0); + TQRgb topRight = (x < srcWidth && y > 0) ? pixel : tqRgba(0, 0, 0, 0); + TQRgb bottomRight = (x < srcWidth && y < srcHeight) ? pixel : tqRgba(0, 0, 0, 0); + + // Bi-linear interpolation. Images are in pre-multiplied form. + int red = static_cast<int>(a * b * tqRed(topLeft) + + a * (1 - b) * tqRed(bottomLeft) + + (1 - a) * b * tqRed(topRight) + + (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5); + int green = static_cast<int>(a * b * tqGreen(topLeft) + + a * (1 - b) * tqGreen(bottomLeft) + + (1 - a) * b * tqGreen(topRight) + + (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5); + int blue = static_cast<int>(a * b * tqBlue(topLeft) + + a * (1 - b) * tqBlue(bottomLeft) + + (1 - a) * b * tqBlue(topRight) + + (1 - a) * (1 - b) * tqBlue(bottomRight) + 0.5); + int alpha = static_cast<int>(a * b * tqAlpha(topLeft) + + a * (1 - b) * tqAlpha(bottomLeft) + + (1 - a) * b * tqAlpha(topRight) + + (1 - a) * (1 - b) * tqAlpha(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, tqRgba(red, green, blue, alpha)); + } + } + + return outputImage; +} + +TQImage KisBrush::interpolate(const TQImage& image1, const TQImage& 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(); + + TQImage outputImage(width, height, 32); + outputImage.setAlphaBuffer(true); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + TQRgb image1pixel = image1.pixel(x, y); + TQRgb image2pixel = image2.pixel(x, y); + + // Images are in pre-multiplied alpha format. + int red = static_cast<int>((1 - t) * tqRed(image1pixel) + t * tqRed(image2pixel) + 0.5); + int green = static_cast<int>((1 - t) * tqGreen(image1pixel) + t * tqGreen(image2pixel) + 0.5); + int blue = static_cast<int>((1 - t) * tqBlue(image1pixel) + t * tqBlue(image2pixel) + 0.5); + int alpha = static_cast<int>((1 - t) * tqAlpha(image1pixel) + t * tqAlpha(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, tqRgba(red, green, blue, alpha)); + } + } + + return outputImage; +} + +KisBrush::ScaledBrush::ScaledBrush() +{ + m_tqmask = 0; + m_image = TQImage(); + m_scale = 1; + m_xScale = 1; + m_yScale = 1; +} + +KisBrush::ScaledBrush::ScaledBrush(KisAlphaMaskSP scaledMask, const TQImage& scaledImage, double scale, double xScale, double yScale) +{ + m_tqmask = 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++) { + + TQRgb pixel = m_image.pixel(x, y); + + int red = tqRed(pixel); + int green = tqGreen(pixel); + int blue = tqBlue(pixel); + int alpha = tqAlpha(pixel); + + red = (red * alpha) / 255; + green = (green * alpha) / 255; + blue = (blue * alpha) / 255; + + m_image.setPixel(x, y, tqRgba(red, green, blue, alpha)); + } + } + } +} + +void KisBrush::setImage(const TQImage& img) +{ + m_img = img; + m_img.detach(); + + setWidth(img.width()); + setHeight(img.height()); + + m_scaledBrushes.clear(); + + setValid(true); +} + +TQ_INT32 KisBrush::width() const +{ + return m_width; +} + +void KisBrush::setWidth(TQ_INT32 w) +{ + m_width = w; +} + +TQ_INT32 KisBrush::height() const +{ + return m_height; +} + +void KisBrush::setHeight(TQ_INT32 h) +{ + m_height = h; +} + +/*TQImage KisBrush::outline(double pressure) { + KisLayerSP layer = image(KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""), + KisPaintInformation(pressure)); + KisBoundary bounds(layer.data()); + int w = tqmaskWidth(pressure); + int h = tqmaskHeight(pressure); + + bounds.generateBoundary(w, h); + TQPixmap pix(bounds.pixmap(w, h)); + TQImage result; + result = pix; + return result; +}*/ + +void KisBrush::generateBoundary() { + KisPaintDeviceSP dev; + int w = tqmaskWidth(KisPaintInformation()); + int h = tqmaskHeight(KisPaintInformation()); + + if (brushType() == IMAGE || brushType() == PIPE_IMAGE) { + dev = image(KisMetaRegistry::instance()->csRegistry() ->getColorSpace(KisID("RGBA",""),""), KisPaintInformation()); + } else { + KisAlphaMaskSP atqmask = tqmask(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(), atqmask->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; + + TQImage 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++) { + TQRgb c = m_img.pixel(x, y); + int a = (tqGray(c) * tqAlpha(c)) / 255; // tqGray(black) = 0 + img.setPixel(x, y, tqRgba(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/chalk/core/kis_brush.h b/chalk/core/kis_brush.h new file mode 100644 index 00000000..091cebde --- /dev/null +++ b/chalk/core/kis_brush.h @@ -0,0 +1,192 @@ +/* + * 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 <tqcstring.h> +#include <tqstring.h> +#include <tqsize.h> +#include <tqimage.h> +#include <tqvaluevector.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 TQPoint; +class TQPixmap; +class KisBoundary; +class KisColorSpace; +class TQIODevice; + +enum enumBrushType { + INVALID, + MASK, + IMAGE, + PIPE_MASK, + PIPE_IMAGE, + AIRBRUSH +}; + +class KRITACORE_EXPORT KisBrush : public KisResource { + typedef KisResource super; + Q_OBJECT + TQ_OBJECT + +public: + /// Construct brush to load filename later as brush + KisBrush(const TQString& filename); + /// Load brush from the specified data, at position dataPos, and set the filename + KisBrush(const TQString& filename, + const TQByteArray & data, + TQ_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 TQImage (handy when you need to copy a brush!) + KisBrush(const TQImage& image, const TQString& name = TQString("")); + + virtual ~KisBrush(); + + virtual bool load(); + /// synchronous, doesn't emit any signal (none defined!) + virtual bool save(); + virtual TQImage img(); + virtual bool saveToDevice(TQIODevice* dev) const; + + /** + @return a tqmask computed from the grey-level values of the + pixels in the brush. + */ + virtual KisAlphaMaskSP tqmask(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 tqmask/image at a given pressure. + TQ_INT32 tqmaskWidth(const KisPaintInformation& info) const; + TQ_INT32 tqmaskHeight(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(); + TQ_INT32 width() const; + TQ_INT32 height() const; + + virtual enumBrushType brushType() const; + + //TQImage 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(TQ_INT32 w); + void setHeight(TQ_INT32 h); + void setImage(const TQImage& img); + void setBrushType(enumBrushType type) { m_brushType = type; }; + static double scaleForPressure(double pressure); + +private: + class ScaledBrush { + public: + ScaledBrush(); + ScaledBrush(KisAlphaMaskSP scaledMask, const TQImage& 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 tqmask() const { return m_tqmask; } + TQImage image() const { return m_image; } + + private: + KisAlphaMaskSP m_tqmask; + TQImage 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; + TQImage scaleImage(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const; + + static TQImage scaleImage(const TQImage& srcImage, int width, int height); + static TQImage interpolate(const TQImage& image1, const TQImage& image2, double t); + + static KisAlphaMaskSP scaleSinglePixelMask(double scale, TQ_UINT8 tqmaskValue, double subPixelX, double subPixelY); + static TQImage scaleSinglePixelImage(double scale, TQRgb 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(); + + TQByteArray m_data; + bool m_ownData; + KisPoint m_hotSpot; + double m_spacing; + bool m_useColorAsMask; + bool m_hasColor; + TQImage m_img; + mutable TQValueVector<ScaledBrush> m_scaledBrushes; + + TQ_INT32 m_width; + TQ_INT32 m_height; + + TQ_UINT32 m_header_size; /* header_size = sizeof (BrushHeader) + brush name */ + TQ_UINT32 m_version; /* brush file version # */ + TQ_UINT32 m_bytes; /* depth of brush in bytes */ + TQ_UINT32 m_magic_number; /* GIMP brush magic number */ + + enumBrushType m_brushType; + + KisBoundary* m_boundary; + +}; +#endif // KIS_BRUSH_ + diff --git a/chalk/core/kis_change_profile_visitor.h b/chalk/core/kis_change_profile_visitor.h new file mode 100644 index 00000000..dea0eb4b --- /dev/null +++ b/chalk/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/chalk/core/kis_colorspace_convert_visitor.h b/chalk/core/kis_colorspace_convert_visitor.h new file mode 100644 index 00000000..e42c0893 --- /dev/null +++ b/chalk/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, TQ_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; + TQ_INT32 m_renderingIntent; +}; + +KisColorSpaceConvertVisitor::KisColorSpaceConvertVisitor(KisColorSpace *dstColorSpace, TQ_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/chalk/core/kis_command.cc b/chalk/core/kis_command.cc new file mode 100644 index 00000000..11da634b --- /dev/null +++ b/chalk/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 <tqstring.h> +#include "kis_command.h" + +KisCommand::KisCommand(KisUndoAdapter *adapter) +{ + m_name = ""; + m_undoAdapter = adapter; +} + +KisCommand::KisCommand(const TQString& name, KisUndoAdapter *adapter) +{ + m_name = name; + m_undoAdapter = adapter; +} + +KisCommand::~KisCommand() +{ +} + +TQString KisCommand::name() const +{ + return m_name; +} + diff --git a/chalk/core/kis_command.h b/chalk/core/kis_command.h new file mode 100644 index 00000000..bc87bc5d --- /dev/null +++ b/chalk/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 <tqstring.h> +#include <kcommand.h> + +class KisUndoAdapter; + +class KisCommand : public KCommand { + typedef KCommand super; + +public: + KisCommand(KisUndoAdapter *undoAdapter); + KisCommand(const TQString& name, KisUndoAdapter *undoAdapter); + virtual ~KisCommand(); + + virtual void execute() = 0; + virtual void unexecute() = 0; + virtual TQString name() const; + +protected: + KisUndoAdapter *adapter() const; + +private: + KisUndoAdapter *m_undoAdapter; + TQString m_name; +}; + +inline +KisUndoAdapter *KisCommand::adapter() const +{ + return m_undoAdapter; +} + +#endif // KIS_COMMAND_H_ + diff --git a/chalk/core/kis_convolution_painter.cc b/chalk/core/kis_convolution_painter.cc new file mode 100644 index 00000000..ea0351df --- /dev/null +++ b/chalk/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 "tqbrush.h" +#include "tqcolor.h" +#include "tqfontinfo.h" +#include "tqfontmetrics.h" +#include "tqpen.h" +#include "tqregion.h" +#include "tqwmatrix.h" +#include <tqimage.h> +#include <tqmap.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqpointarray.h> +#include <tqrect.h> +#include <tqstring.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <tqcolor.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::fromTQImage(const TQImage& 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 TQ_INT32[count]; + TQ_INT32* itData = k->data; + TQ_UINT8* itImg = (TQ_UINT8*)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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, + KisConvolutionBorderOp borderOp, + KisChannelInfo::enumChannelFlags channelFlags ) +{ + // Make the area we cover as small as possible + if (m_device->hasSelection()) { + + TQRect r = m_device->selection()->selectedRect().intersect(TQRect(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 + TQ_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(); + TQ_UINT8** pixelPtrCache = new TQ_UINT8*[cacheSize]; + for (int i = 0; i < cacheSize; i++) + pixelPtrCache[i] = new TQ_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) + { + TQ_INT32 i = 0; + for (TQ_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 (TQ_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left + TQ_UINT8** d = pixelPtrCache + krow * kw; + //memmove( d, d + 1, (kw-1)*sizeof(TQ_UINT8*)); + for (int i = 0; i < (kw-1); i++) { + memcpy(d[i], d[i+1], cdepth); + } + } + TQ_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(TQRect(x, y, w, h)); + + emit notifyProgressDone(); + + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; +} + +void KisConvolutionPainter::applyMatrixRepeat(KisKernelSP kernel, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, + KisChannelInfo::enumChannelFlags channelFlags) +{ + int lastProgressPercent = 0; + // Determine the kernel's extent from the center pixel + TQ_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(); + TQ_UINT8** pixelPtrCache = new TQ_UINT8*[cacheSize]; + for (int i = 0; i < cacheSize; i++) + pixelPtrCache[i] = new TQ_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; + + TQ_INT32 itStart = row - khalfHeight; + TQ_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 + { + TQ_INT32 i = 0; + TQ_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 + TQ_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; + } + TQ_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 + TQ_INT32 itHStart = col - khalfWidth; + TQ_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; + } + } + TQ_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 (TQ_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left + TQ_UINT8** d = pixelPtrCache + krow * kw; +// memmove( d, d + 1, (kw-1)*sizeof(TQ_UINT8*)); + for (int i = 0; i < (kw-1); i++) { + memcpy(d[i], d[i+1], cdepth); + } + }*/ + TQ_UINT8* firstincache = pixelPtrCache[0]; + memmove(pixelPtrCache, pixelPtrCache + 1, (cacheSize - 1) * sizeof(TQ_UINT8*) ); + pixelPtrCache[cacheSize - 1] = firstincache; + if(col < xLastMinuskhw) + { + TQ_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; + } + TQ_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(TQRect(x, y, w, h)); + + emit notifyProgressDone(); + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; +} diff --git a/chalk/core/kis_convolution_painter.h b/chalk/core/kis_convolution_painter.h new file mode 100644 index 00000000..8c0b2bba --- /dev/null +++ b/chalk/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 <tqimage.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: + + TQ_UINT32 width; + TQ_UINT32 height; + TQ_INT32 offset; + TQ_INT32 factor; + TQ_INT32 * data; + + KisKernel() : width(0), height(0), offset(0), factor(0), data(0) {}; + + virtual ~KisKernel() { delete [] data; }; + + static KisKernelSP fromTQImage(const TQImage& 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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, + KisChannelInfo::enumChannelFlags channelFlags); + + +}; +#endif //KIS_CONVOLUTION_PAINTER_H_ diff --git a/chalk/core/kis_crop_visitor.h b/chalk/core/kis_crop_visitor.h new file mode 100644 index 00000000..0988a920 --- /dev/null +++ b/chalk/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 "tqrect.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 TQRect & 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: + TQRect m_rect; + bool m_movelayers; +}; + + +#endif diff --git a/chalk/core/kis_datamanager.h b/chalk/core/kis_datamanager.h new file mode 100644 index 00000000..79f7ddb8 --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqvaluevector.h> +#include <tqrect.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 TQ_UINT8's a single pixel takes. + */ +class KisDataManager : public ACTUAL_DATAMGR { + +public: + KisDataManager(TQ_UINT32 pixelSize, const TQ_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 TQ_UINT8 *defPixel) { return ACTUAL_DATAMGR::setDefaultPixel(defPixel); } + + /** + * Gets the default pixel. + */ + inline const TQ_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 TQ_UINT32 pixelSize() { return ACTUAL_DATAMGR::pixelSize(); } + + /** + * Return the extent of the data in x,y,w,h. + */ + inline void extent(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const + { return ACTUAL_DATAMGR::extent(x, y, w, h); } + + TQRect extent() const { return ACTUAL_DATAMGR::extent(); } + + +public: + + /** + * Crop or extend the data to x, y, w, h. + */ + inline void setExtent(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) + { return ACTUAL_DATAMGR::setExtent(x, y, w, h); } + + inline void setExtent(const TQRect & rect) { setExtent(rect.x(), rect.y(), rect.width(), rect.height()); } + +public: + + /** + * Clear the specified rect to the specified value. + */ + inline void clear(TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_INT32 h, + TQ_UINT8 def) { ACTUAL_DATAMGR::clear(x, y, w, h, def); } + + /** + * Clear the specified rect to the specified pixel value. + */ + inline void clear(TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_INT32 h, + const TQ_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, TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 dx, TQ_INT32 dy, + TQ_INT32 w, TQ_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 TQ_UINT8* pixel(TQ_INT32 x, TQ_INT32 y) + { return ACTUAL_DATAMGR::pixel(x, y); } + + /** + * Get a read-write pointer to the specified pixel. + */ + inline KDE_DEPRECATED TQ_UINT8* writablePixel(TQ_INT32 x, TQ_INT32 y) + { return ACTUAL_DATAMGR::writablePixel(x, y); } + + /** + * Write the specified data to x, y. There is no checking on pixelSize! + */ + inline void setPixel(TQ_INT32 x, TQ_INT32 y, const TQ_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 TQValueVector? + */ + inline void readBytes(TQ_UINT8 * data, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_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 TQValueVector? + */ + inline void writeBytes(const TQ_UINT8 * data, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_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 TQ_INT32 numContiguousColumns(TQ_INT32 x, TQ_INT32 minY, TQ_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 TQ_INT32 numContiguousRows(TQ_INT32 y, TQ_INT32 minX, TQ_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 TQ_INT32 rowStride(TQ_INT32 x, TQ_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/chalk/core/kis_exif_info.cc b/chalk/core/kis_exif_info.cc new file mode 100644 index 00000000..5d54ed5e --- /dev/null +++ b/chalk/core/kis_exif_info.cc @@ -0,0 +1,66 @@ +/* + * This file is part of Chalk + * + * 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 TQDomElement& elmt) +{ + if(elmt.tagName() != "ExifInfo") + return false; + for( TQDomNode node = elmt.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + TQDomElement e = node.toElement(); + if ( !e.isNull() ) + { + if(e.tagName() == "ExifValue") + { + TQString key = e.attribute("name"); + ExifValue eV; + eV.load(e); + setValue(key, eV); + } + } + } + return true; +} + +TQDomElement KisExifInfo::save(TQDomDocument& doc) +{ + TQDomElement elmt = doc.createElement("ExifInfo"); + for( KisExifInfo::evMap::const_iterator it = begin(); it != end(); ++it) + { + ExifValue ev = it.data(); + TQDomElement evD = ev.save( doc); + evD.setAttribute("name", it.key()); + elmt.appendChild(evD); + } + return elmt; +} diff --git a/chalk/core/kis_exif_info.h b/chalk/core/kis_exif_info.h new file mode 100644 index 00000000..d3fa3428 --- /dev/null +++ b/chalk/core/kis_exif_info.h @@ -0,0 +1,58 @@ +/* + * This file is part of Chalk + * + * 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 <tqdom.h> +#include <tqmap.h> + +class KisExifInfo +{ + public: + KisExifInfo(); + virtual ~KisExifInfo(); + + virtual bool load(const TQDomElement& elmt); + virtual TQDomElement save(TQDomDocument& doc); + + bool getValue(TQString name, ExifValue& value) + { + if ( m_values.tqfind( name ) == m_values.end() ) { + return false; + } + else { + value = m_values[name]; + return true; + } + } + void setValue(TQString name, ExifValue value) + { + m_values[name] = value; + } + typedef TQMap<TQString, 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/chalk/core/kis_exif_value.cc b/chalk/core/kis_exif_value.cc new file mode 100644 index 00000000..1b7bcf32 --- /dev/null +++ b/chalk/core/kis_exif_value.cc @@ -0,0 +1,678 @@ +/* + * This file is part of Chalk + * + * 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 TQ_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, TQ_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, TQ_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 TQ_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, TQ_UINT64* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + *value = (((TQ_UINT64)data[0] << 56) | ((TQ_UINT64)data[1] << 48) | ((TQ_UINT64)data[2] << 40) | ((TQ_UINT64)data[3] << 32) | ((TQ_UINT64)data[4] << 24) | ((TQ_UINT64)data[5] << 16) | ((TQ_UINT64)data[6] << 8) | (TQ_UINT64)data[7]); + break; + case ExifValue::BYTE_ORDER_INTEL: + *value = (((TQ_UINT64)data[7] << 56) | ((TQ_UINT64)data[6] << 48) | ((TQ_UINT64)data[5] << 40) | ((TQ_UINT64)data[4] << 32) | ((TQ_UINT64)data[3] << 24) | ((TQ_UINT64)data[2] << 16) | ((TQ_UINT64)data[1] << 8) | (TQ_UINT64)data[0]); + } +} + +void set64Bit(unsigned char *data, ExifValue::ByteOrder order, const TQ_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 TQString(); + } else if ( type() == EXIF_TYPE_UNDEFINED) + { + m_value = new UByteArray(); + } +} + +bool ExifValue::load(const TQDomElement& elmt) +{ + TQString 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(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_UINT8)0); + } else { + setValue(i, (TQ_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(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_UINT16)0); + } else { + setValue(i, (TQ_UINT16) attr.toUInt()); + } + } + break; + case EXIF_TYPE_LONG: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_UINT32)0); + } else { + setValue(i, (TQ_UINT32) attr.toUInt()); + } + } + break; + case EXIF_TYPE_RATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifRational r; + if( (attr = elmt.attribute(TQString("numerator%1").tqarg(i) ) ).isNull() ) + { + r.numerator = (TQ_UINT32)0; + } else { + r.numerator = (TQ_UINT32) attr.toUInt(); + } + if( (attr = elmt.attribute(TQString("denominator%1").tqarg(i) ) ).isNull() ) + { + r.denominator = (TQ_UINT32)0; + } else { + r.denominator = (TQ_UINT32) attr.toUInt(); + } + setValue(i, r); + } + break; + case EXIF_TYPE_SBYTE: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_INT8)0); + } else { + setValue(i, (TQ_INT8) attr.toInt()); + } + } + break; + case EXIF_TYPE_UNDEFINED: + { + TQString instr = elmt.attribute("value"); + TQByteArray out; + TQByteArray 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(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_INT16)0); + } else { + setValue(i, (TQ_INT16) attr.toInt()); + } + } + break; + case EXIF_TYPE_SLONG: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (TQ_INT32)0); + } else { + setValue(i, (TQ_INT32) attr.toInt()); + } + } + break; + case EXIF_TYPE_SRATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifSRational r; + if( (attr = elmt.attribute(TQString("numerator%1").tqarg(i) ) ).isNull() ) + { + r.numerator = (TQ_INT32)0; + } else { + r.numerator = (TQ_INT32) attr.toInt(); + } + if( (attr = elmt.attribute(TQString("denominator%1").tqarg(i) ) ).isNull() ) + { + r.denominator = (TQ_UINT32)0; + } else { + r.denominator = (TQ_UINT32) attr.toInt(); + } + setValue(i, r); + } + break; + case EXIF_TYPE_FLOAT: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(TQString("value%1").tqarg(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(TQString("value%1").tqarg(i) ) ).isNull() ) + { + setValue(i, (double)0); + } else { + setValue(i, (double) attr.toDouble()); + } + } + break; + case EXIF_TYPE_UNKNOW: + break; + + } + return true; +} + +TQDomElement ExifValue::save(TQDomDocument& doc) +{ + TQDomElement 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(TQString("value%1").tqarg(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(TQString("value%1").tqarg(i), asShort( i ) ); + break; + case EXIF_TYPE_LONG: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(i), asLong( i ) ); + break; + case EXIF_TYPE_RATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifRational r = asRational(i); + elmt.setAttribute(TQString("numerator%1").tqarg(i), r.numerator ); + elmt.setAttribute(TQString("denominator%1").tqarg(i), r.denominator ); + } + break; + case EXIF_TYPE_SBYTE: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(i), asSByte( i ) ); + break; + case EXIF_TYPE_UNDEFINED: + { + UByteArray value = asUndefined(); + TQByteArray data; + data.setRawData((char*)value.data(), value.size()); + TQByteArray tqencodedData; + KCodecs::base64Encode( data, tqencodedData ); + data.resetRawData( (char*)value.data(), value.size()); + elmt.setAttribute("value", TQString(tqencodedData)); + } + break; + case EXIF_TYPE_SSHORT: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(i), asSShort( i ) ); + break; + case EXIF_TYPE_SLONG: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(i), asSLong( i ) ); + break; + case EXIF_TYPE_SRATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifSRational r = asSRational(i); + elmt.setAttribute(TQString("numerator%1").tqarg(i), r.numerator ); + elmt.setAttribute(TQString("denominator%1").tqarg(i), r.denominator ); + } + break; + case EXIF_TYPE_FLOAT: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(i), asFloat( i ) ); + break; + case EXIF_TYPE_DOUBLE: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(TQString("value%1").tqarg(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 = ((TQ_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, (TQ_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, (TQ_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, (TQ_UINT32*)&n.m_srational.numerator); + get32Bit( data + 8 * i + 4, order, (TQ_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, (TQ_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, (TQ_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: + { + TQString 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++) + { + *(((TQ_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, (TQ_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, (TQ_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, (TQ_UINT32*)&asExifNumber(i).m_srational.numerator); + set32Bit( (*data) + 4 * i + 4, order, (TQ_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, (TQ_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, (TQ_UINT64*)&asExifNumber(i).m_double); + } + return; + case EXIF_TYPE_UNKNOW: + break; + } +} + +TQString ExifValue::toString() +{ + switch(type()) + { + case EXIF_TYPE_ASCII: + return asAscii(); + case EXIF_TYPE_UNDEFINED: + { + TQString undefined = "undefined"; + UByteArray array = asUndefined(); + for(uint i = 0; i < components(); i++) + { + undefined += "\\" + TQString().setNum( array[i] ); + } + return undefined; + } + default: + { + TQString str = ""; + for(uint i = 0; i < components(); i++) + { + str += toString(i); + } + return str; + } + } +} + +TQString ExifValue::toString(uint i) +{ + switch(type()) + { + case EXIF_TYPE_BYTE: + return TQString("%1 ").tqarg( asExifNumber( i ).m_byte ); + case EXIF_TYPE_SHORT: + return TQString("%1 ").tqarg( asExifNumber( i ).m_short ); + case EXIF_TYPE_LONG: + return TQString("%1 ").tqarg( asExifNumber( i ).m_long ); + case EXIF_TYPE_RATIONAL: + return TQString("%1 / %2 ").tqarg( asExifNumber( i ).m_rational.numerator ).tqarg( asExifNumber( i ).m_rational.denominator ); + case EXIF_TYPE_SBYTE: + return TQString("%1 ").tqarg( asExifNumber( i ).m_sbyte ); + case EXIF_TYPE_SSHORT: + return TQString("%1 ").tqarg( asExifNumber( i ).m_sshort ); + case EXIF_TYPE_SLONG: + return TQString("%1 ").tqarg( asExifNumber( i ).m_slong ); + case EXIF_TYPE_SRATIONAL: + return TQString("%1 / %2 ").tqarg( asExifNumber( i ).m_srational.numerator ).tqarg( asExifNumber( i ).m_srational.denominator ); + case EXIF_TYPE_FLOAT: + return TQString("%1 ").tqarg( asExifNumber( i ).m_float ); + case EXIF_TYPE_DOUBLE: + return TQString("%1 ").tqarg( asExifNumber( i ).m_double ); + default: + return "unknow "; + } +} + diff --git a/chalk/core/kis_exif_value.h b/chalk/core/kis_exif_value.h new file mode 100644 index 00000000..44a3632b --- /dev/null +++ b/chalk/core/kis_exif_value.h @@ -0,0 +1,270 @@ +/* + * This file is part of Chalk + * + * 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 <tqdom.h> + +#include <tqcstring.h> +#include <tqstring.h> + +typedef TQMemArray<TQ_UINT8> UByteArray; + +struct KisExifRational { + TQ_UINT32 numerator; + TQ_UINT32 denominator; +}; + +struct KisExifSRational { + TQ_INT32 numerator; + TQ_INT32 denominator; +}; + +class ExifValue { + typedef union { + TQ_UINT8 m_byte; + TQ_UINT16 m_short; + TQ_UINT32 m_long; + KisExifRational m_rational; + TQ_INT8 m_sbyte; + TQ_INT16 m_sshort; + TQ_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 TQDomElement& elmt); + virtual TQDomElement save(TQDomDocument& 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 TQString asAscii() { + if(m_type == EXIF_TYPE_ASCII) + return TQString(*(TQString*) m_value); + return TQString(); + } + inline void setAsAscii(char* data) + { + if(m_type == EXIF_TYPE_ASCII) + { + TQString str = TQString((char*) data); + *(TQString*)m_value = str; + m_components = str.length(); + } + } + inline void setAsAscii(TQString str) + { + *(TQString*)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. + */ + TQString toString(); + + inline TQ_UINT8 asByte(uint i) + { + if(m_type == EXIF_TYPE_BYTE) + return asExifNumber(i).m_byte; + return 0; + } + inline void setValue(uint i, TQ_UINT8 v) + { + ((ExifNumber*)m_value)[i].m_byte = v; + } + inline TQ_UINT8 asShort(uint i) + { + if(m_type == EXIF_TYPE_SHORT) + return asExifNumber(i).m_short; + return 0; + } + inline void setValue(uint i, TQ_UINT16 v) + { + ((ExifNumber*)m_value)[i].m_short = v; + } + inline TQ_UINT8 asLong(uint i) + { + if(m_type == EXIF_TYPE_LONG) + return asExifNumber(i).m_long; + return 0; + } + inline void setValue(uint i, TQ_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, TQ_UINT32 n, TQ_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 TQ_INT8 asSByte(uint i) + { + if(m_type == EXIF_TYPE_SBYTE) + return asExifNumber(i).m_sbyte; + return 0; + } + inline void setValue(uint i, TQ_INT8 v) + { + ((ExifNumber*)m_value)[i].m_sbyte = v; + } + inline TQ_INT16 asSShort(uint i) + { + if(m_type == EXIF_TYPE_SSHORT) + return asExifNumber(i).m_sshort; + return 0; + } + inline void setValue(uint i, TQ_INT16 v) + { + ((ExifNumber*)m_value)[i].m_sshort = v; + } + inline TQ_INT32 asSLong(uint i) + { + if(m_type == EXIF_TYPE_SLONG) + return asExifNumber(i).m_slong; + return 0; + } + inline void setValue(uint i, TQ_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, TQ_INT32 n, TQ_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. + */ + TQString 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/chalk/core/kis_fill_painter.cc b/chalk/core/kis_fill_painter.cc new file mode 100644 index 00000000..5b8fdcc9 --- /dev/null +++ b/chalk/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 "tqbrush.h" +#include "tqfontinfo.h" +#include "tqfontmetrics.h" +#include "tqpen.h" +#include "tqregion.h" +#include "tqwmatrix.h" +#include <tqimage.h> +#include <tqmap.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqpointarray.h> +#include <tqrect.h> +#include <tqstring.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(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, const KisColor& kc, TQ_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()); + TQ_UINT8 * data = kc2.data(); + m_device->colorSpace()->setAlpha(data, opacity, 1); + + m_device->fill(x1, y1, w, h, data); + + addDirtyRect(TQRect(x1, y1, w, h)); + } +} + +void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_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 = TQMIN((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 = TQMIN((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(TQRect(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; + } + + TQRect 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* tqparent*/) : x(x), y(y)/*, tqparent(tqparent)*/ {} + int x; + int y; +// FillSegment* tqparent; +}; + +typedef enum { None = 0, Added = 1, Checked = 2 } tqStatus; + +KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY) { + if (m_width < 0 || m_height < 0) { + if (m_device->hasSelection() && m_careForSelection) { + + TQRect 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(); + + TQ_UINT8* source = new TQ_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*/)); + + tqStatus* map = new tqStatus[m_size]; + + memset(map, None, m_size * sizeof(tqStatus)); + + 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; + TQ_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->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromTQColor(TQt::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->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromTQColor(TQt::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->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromTQColor(TQt::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/chalk/core/kis_fill_painter.h b/chalk/core/kis_fill_painter.h new file mode 100644 index 00000000..da244a67 --- /dev/null +++ b/chalk/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 <tqrect.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(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h); + /** + * Overloaded version of the above function. + */ + void eraseRect(const TQRect& rc); + + /** + * Fill a rectangle with a certain color. + */ + void fillRect(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const KisColor& c); + /** + * Overloaded version of the above function. + */ + void fillRect(const TQRect& rc, const KisColor& c); + + /** + * Fill a rectangle with a certain color and opacity. + */ + void fillRect(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const KisColor& c, TQ_UINT8 opacity); + /** + * Overloaded version of the above function. + */ + void fillRect(const TQRect& rc, const KisColor& c, TQ_UINT8 opacity); + + /** + * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the + * entire rectangle. + */ + void fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, KisPattern * pattern); + /** + * Overloaded version of the above function. + */ + void fillRect(const TQRect& 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 tqmask 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; + TQRect m_rect; + bool m_sampleMerged; + bool m_careForSelection; + bool m_fuzzy; +}; + + +inline +void KisFillPainter::fillRect(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const KisColor& c) +{ + fillRect(x, y, w, h, c, OPACITY_OPAQUE); +} + +inline +void KisFillPainter::fillRect(const TQRect& rc, const KisColor& c) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE); +} + +inline +void KisFillPainter::eraseRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h) +{ + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + KisColor c(TQt::black, cs); + fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT); +} + +inline +void KisFillPainter::eraseRect(const TQRect& rc) +{ + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + KisColor c(TQt::black, cs); + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT); +} + +inline +void KisFillPainter::fillRect(const TQRect& rc, const KisColor& c, TQ_UINT8 opacity) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); +} + +inline +void KisFillPainter::fillRect(const TQRect& 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/chalk/core/kis_filter.cc b/chalk/core/kis_filter.cc new file mode 100644 index 00000000..ee396596 --- /dev/null +++ b/chalk/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 <tqstring.h> + +#include "kis_types.h" +#include "kis_filter_configuration.h" + +KisFilter::KisFilter(const KisID& id, const TQString & category, const TQString & entry) + : KisProgressSubject(0, id.id().latin1()) + , m_id(id) + , m_progressDisplay(0) + , m_category(category) + , m_entry(entry) +{ +} + +KisFilterConfiguration * KisFilter::configuration(TQWidget*) +{ + return new KisFilterConfiguration(m_id.id(), 0); +} + +KisFilterConfiguration * KisFilter::configuration() +{ + return new KisFilterConfiguration(m_id.id(), 0); +} + +KisFilterConfigWidget * KisFilter::createConfigurationWidget(TQWidget *, 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(TQ_INT32 totalSteps) +{ + if (m_progressEnabled) { + + m_progressTotalSteps = totalSteps; + m_lastProgressPerCent = 0; + m_progressSteps = 0; + emit notifyProgress(0); + } +} + +void KisFilter::setProgress(TQ_INT32 progress) +{ + if (m_progressEnabled) { + TQ_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 TQString& stage, TQ_INT32 progress) +{ + if (m_progressEnabled) { + + TQ_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; +} + +TQRect KisFilter::enlargeRect(TQRect 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/chalk/core/kis_filter.h b/chalk/core/kis_filter.h new file mode 100644 index 00000000..807ecff8 --- /dev/null +++ b/chalk/core/kis_filter.h @@ -0,0 +1,201 @@ +/* + * 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 <tqstring.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 TQWidget; + +/** + * Basic interface of a Chalk filter. + */ +class KRITACORE_EXPORT KisFilter : public KisProgressSubject, public KShared { + Q_OBJECT + TQ_OBJECT +public: + + /** + * Construct a Chalk filter + */ + KisFilter(const KisID& id, const TQString & category, const TQString & entry); + virtual ~KisFilter() {} + +public: + + virtual void setProgressDisplay(KisProgressDisplayInterface * progressDisplay); + + virtual void process(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisFilterConfiguration*, const TQRect&) = 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(TQWidget*); + + /** + * @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 TQRect enlargeRect(TQRect 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 TQString menuCategory() const { return m_category; }; + // The i18n'ed string this filter wants to show itself in the menu + + inline TQString menuEntry() const { return m_entry; }; + + /** + * Create the configuration widget for this filter. + * + * @param tqparent the TQt 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(TQWidget * tqparent, 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(TQ_INT32 totalSteps); + void setProgress(TQ_INT32 progress); + void incProgress(); + void setProgressStage(const TQString& stage, TQ_INT32 progress); + void setProgressDone(); + inline TQ_INT32 progress() { return m_progressSteps; } +private: + bool m_cancelRequested; + bool m_progressEnabled; + bool m_autoUpdate; + +protected: + TQ_INT32 m_progressTotalSteps; + TQ_INT32 m_lastProgressPerCent; + TQ_INT32 m_progressSteps; + + KisID m_id; + KisProgressDisplayInterface * m_progressDisplay; + TQString m_category; // The category in the filter menu this filter fits + TQString m_entry; // the i18n'ed accelerated menu text + +}; + + +#endif diff --git a/chalk/core/kis_filter_config_widget.cc b/chalk/core/kis_filter_config_widget.cc new file mode 100644 index 00000000..df7c13d5 --- /dev/null +++ b/chalk/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(TQWidget * tqparent, const char * name, WFlags f) + : TQWidget(tqparent, name, f) +{ +} + +KisFilterConfigWidget::~KisFilterConfigWidget() +{ +} + +#include "kis_filter_config_widget.moc" diff --git a/chalk/core/kis_filter_config_widget.h b/chalk/core/kis_filter_config_widget.h new file mode 100644 index 00000000..ddb600ab --- /dev/null +++ b/chalk/core/kis_filter_config_widget.h @@ -0,0 +1,50 @@ +/* + * 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 <tqwidget.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 TQWidget { + + Q_OBJECT + TQ_OBJECT + +public: + + KisFilterConfigWidget(TQWidget * tqparent, 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/chalk/core/kis_filter_configuration.cc b/chalk/core/kis_filter_configuration.cc new file mode 100644 index 00000000..b25dcb24 --- /dev/null +++ b/chalk/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 <tqdom.h> +#include <tqstring.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 TQString & s ) +{ + m_properties.clear(); + + TQDomDocument doc; + doc.setContent( s ); + TQDomElement e = doc.documentElement(); + TQDomNode 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... + TQDomElement e = n.toElement(); + TQString name; + TQString type; + TQString 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] = TQVariant(value); + } + } + n = n.nextSibling(); + } + //dump(); +} + +TQString KisFilterConfiguration::toString() +{ + TQDomDocument doc = TQDomDocument("filterconfig"); + TQDomElement root = doc.createElement( "filterconfig" ); + root.setAttribute( "name", m_name ); + root.setAttribute( "version", m_version ); + + doc.appendChild( root ); + + TQMap<TQString, TQVariant>::Iterator it; + for ( it = m_properties.begin(); it != m_properties.end(); ++it ) { + TQDomElement e = doc.createElement( "property" ); + e.setAttribute( "name", it.key().latin1() ); + TQVariant v = it.data(); + e.setAttribute( "type", v.typeName() ); + TQString s = v.asString(); + TQDomText text = doc.createCDATASection(v.asString() ); // XXX: Unittest this! + e.appendChild(text); + root.appendChild(e); + } + + return doc.toString(); +} + +const TQString & KisFilterConfiguration::name() const +{ + return m_name; +} + +TQ_INT32 KisFilterConfiguration::version() const +{ + return m_version; +} + +void KisFilterConfiguration::setProperty(const TQString & name, const TQVariant & value) +{ + if ( m_properties.tqfind( name ) == m_properties.end() ) { + m_properties.insert( name, value ); + } + else { + m_properties[name] = value; + } +} + +bool KisFilterConfiguration::getProperty(const TQString & name, TQVariant & value) +{ + if ( m_properties.tqfind( name ) == m_properties.end() ) { + return false; + } + else { + value = m_properties[name]; + return true; + } +} + +TQVariant KisFilterConfiguration::getProperty(const TQString & name) +{ + if ( m_properties.tqfind( name ) == m_properties.end() ) { + return TQVariant(); + } + else { + return m_properties[name]; + } +} + + +int KisFilterConfiguration::getInt(const TQString & name, int def) +{ + TQVariant v = getProperty(name); + if (v.isValid()) + return v.asInt(); + else + return def; + +} + +double KisFilterConfiguration::getDouble(const TQString & name, double def) +{ + TQVariant v = getProperty(name); + if (v.isValid()) + return v.asDouble(); + else + return def; +} + +bool KisFilterConfiguration::getBool(const TQString & name, bool def) +{ + TQVariant v = getProperty(name); + if (v.isValid()) + return v.asBool(); + else + return def; +} + +TQString KisFilterConfiguration::getString(const TQString & name, TQString def) +{ + TQVariant v = getProperty(name); + if (v.isValid()) + return v.asString(); + else + return def; +} + +void KisFilterConfiguration::dump() +{ + TQMap<TQString, TQVariant>::Iterator it; + for ( it = m_properties.begin(); it != m_properties.end(); ++it ) { + } + +} diff --git a/chalk/core/kis_filter_configuration.h b/chalk/core/kis_filter_configuration.h new file mode 100644 index 00000000..8361861a --- /dev/null +++ b/chalk/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 <tqstring.h> +#include <tqmap.h> +#include <tqvariant.h> +#include <kdebug.h> +#include "koffice_export.h" + +class KisPreviewDialog; +class KisProgressDisplayInterface; +class KisFilterConfigWidget; +class TQWidget; + +/** + * 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 TQString & name, TQ_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 TQString &); + + /** + * Create a serialized version of this filter config + */ + virtual TQString toString(); + + /** + * Get the unique, language independent name of the filter. + */ + const TQString & name() const; + + /** + * Get the version of the filter that has created this config + */ + TQ_INT32 version() const; + + /** + * Set the property with name to value. + */ + virtual void setProperty(const TQString & name, const TQVariant & value); + + /** + * Set value to the value associated with property name + * @return false if the specified property did not exist. + */ + virtual bool getProperty(const TQString & name, TQVariant & value); + + virtual TQVariant getProperty(const TQString & name); + + int getInt(const TQString & name, int def = 0); + double getDouble(const TQString & name, double def = 0.0); + bool getBool(const TQString & name, bool def = false); + TQString getString(const TQString & name, TQString def = TQString()); + +private: + void dump(); + +protected: + + TQString m_name; + TQ_INT32 m_version; + TQMap<TQString, TQVariant> m_properties; + +}; + +#endif // _KIS_FILTER_CONFIGURATION_H_ diff --git a/chalk/core/kis_filter_registry.cc b/chalk/core/kis_filter_registry.cc new file mode 100644 index 00000000..b7c8383e --- /dev/null +++ b/chalk/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 <tqstring.h> +#include <tqptrlist.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(TQString::tqfromLatin1("Chalk/Filter"), + TQString::tqfromLatin1("(Type == 'Service') and " + "([X-Chalk-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, TQStringList(), &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/chalk/core/kis_filter_registry.h b/chalk/core/kis_filter_registry.h new file mode 100644 index 00000000..9ad9901d --- /dev/null +++ b/chalk/core/kis_filter_registry.h @@ -0,0 +1,53 @@ +/* + * 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 <tqobject.h> + +#include "kis_types.h" +#include "kis_generic_registry.h" + +#include <koffice_export.h> + +class TQString; +class TQStringList; + +class KRITACORE_EXPORT KisFilterRegistry : public TQObject, public KisGenericRegistry<KisFilterSP> +{ + + Q_OBJECT + TQ_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/chalk/core/kis_filter_strategy.cc b/chalk/core/kis_filter_strategy.cc new file mode 100644 index 00000000..f82e6cb5 --- /dev/null +++ b/chalk/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); +} + +TQ_UINT32 KisHermiteFilterStrategy::intValueAt(TQ_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); +} + +TQ_UINT32 KisCubicFilterStrategy::intValueAt(TQ_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); +} + +TQ_UINT32 KisBoxFilterStrategy::intValueAt(TQ_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); +} + +TQ_UINT32 KisTriangleFilterStrategy::intValueAt(TQ_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/chalk/core/kis_filter_strategy.h b/chalk/core/kis_filter_strategy.h new file mode 100644 index 00000000..55ef3d06 --- /dev/null +++ b/chalk/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 TQ_UINT32 intValueAt(TQ_INT32 t) const {return TQ_UINT32(255*valueAt(t/256.0));}; + double support() { return supportVal;}; + TQ_UINT32 intSupport() { return intSupportVal;}; + virtual bool boxSpecial() { return false;}; + protected: + double supportVal; + TQ_UINT32 intSupportVal; + KisID m_id; +}; + +class KisHermiteFilterStrategy : public KisFilterStrategy +{ + public: + KisHermiteFilterStrategy() : KisFilterStrategy(KisID("Hermite", i18n("Hermite"))) + {supportVal = 1.0; intSupportVal = 256;} + virtual ~KisHermiteFilterStrategy() {} + + virtual TQ_UINT32 intValueAt(TQ_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 TQ_UINT32 intValueAt(TQ_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 TQ_UINT32 intValueAt(TQ_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 TQ_UINT32 intValueAt(TQ_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/chalk/core/kis_gradient.cc b/chalk/core/kis_gradient.cc new file mode 100644 index 00000000..efc370bb --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqtextstream.h> +#include <tqfile.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 TQString& 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; +} + +TQImage 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 TQImage& 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(TQValueVector<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, TQColor *color, TQ_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<TQ_UINT8>(col.alpha() * OPACITY_OPAQUE + 0.5); + } +} + +TQImage KisGradient::generatePreview(int width, int height) const +{ + TQImage 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; + + TQColor color; + TQ_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, tqRgb(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(TQColor(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/chalk/core/kis_gradient.h b/chalk/core/kis_gradient.h new file mode 100644 index 00000000..50bc0fd8 --- /dev/null +++ b/chalk/core/kis_gradient.h @@ -0,0 +1,265 @@ +/* + * 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 <tqvaluevector.h> +#include <tqcolor.h> + +#include <kio/job.h> + +#include "kis_resource.h" +#include "kis_global.h" + +class TQImage; + +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 TQColor with KisColor +class Color { + public: + Color() { m_alpha = 0; } + Color(const TQColor& color, double alpha) { m_color = color; m_alpha = alpha; } + + const TQColor& color() const { return m_color; } + double alpha() const { return m_alpha; } + + private: + TQColor 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 + TQ_OBJECT + +public: + KisGradient(const TQString& file); + virtual ~KisGradient(); + + virtual bool load(); + virtual bool save(); + virtual TQImage img(); + virtual TQImage generatePreview(int width, int height) const; + + void colorAt(double t, TQColor *color, TQ_UINT8 *opacity) const; + + KisGradientSegment *segmentAt(double t) const; + +protected: + inline void pushSegment( KisGradientSegment* segment ) { m_segments.push_back(segment); }; + void setImage(const TQImage& img); + + TQValueVector<KisGradientSegment *> m_segments; + +private: + bool init(); + +private: + TQByteArray m_data; + TQImage m_img; +}; + +#endif // KIS_GRADIENT_H + diff --git a/chalk/core/kis_gradient_painter.cc b/chalk/core/kis_gradient_painter.cc new file mode 100644 index 00000000..2258d899 --- /dev/null +++ b/chalk/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 "tqbrush.h" +#include "tqcolor.h" +#include "tqfontinfo.h" +#include "tqfontmetrics.h" +#include "tqpen.h" +#include "tqregion.h" +#include "tqwmatrix.h" +#include <tqimage.h> +#include <tqmap.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqpointarray.h> +#include <tqrect.h> +#include <tqstring.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 = TQMAX(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 tqshape, + enumGradientRepeat repeat, + double antiAliasThreshold, + bool reverseGradient, + TQ_INT32 startx, + TQ_INT32 starty, + TQ_INT32 width, + TQ_INT32 height) +{ + m_cancelRequested = false; + + if (!m_gradient) return false; + + GradientShapeStrategy *tqshapeStrategy = 0; + + switch (tqshape) { + case GradientShapeLinear: + tqshapeStrategy = new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeBiLinear: + tqshapeStrategy = new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeRadial: + tqshapeStrategy = new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeSquare: + tqshapeStrategy = new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeConical: + tqshapeStrategy = new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeConicalSymetric: + tqshapeStrategy = new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + } + Q_CHECK_PTR(tqshapeStrategy); + + 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 + TQRect r; + if( m_device->hasSelection() ) { + r = m_device->selection()->selectedExactRect(); + startx = r.x(); + starty = r.y(); + width = r.width(); + height = r.height(); + } + + TQ_INT32 endx = startx + width - 1; + TQ_INT32 endy = starty + height - 1; + + TQImage 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 = tqshapeStrategy->valueAt( x, y); + t = repeatStrategy->valueAt(t); + + if (reverseGradient) { + t = 1 - t; + } + + TQColor color; + TQ_UINT8 opacity; + + m_gradient->colorAt(t, &color, &opacity); + + layer.setPixel(x - startx, y - starty, + tqRgba(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) { + + TQColor color; + emit notifyProgressStage(i18n("Anti-aliasing gradient..."), lastProgressPercent); + TQ_UINT8 * layerPointer = layer.bits(); + for (int y = starty; y <= endy; y++) { + for (int x = startx; x <= endx; x++) { + + double maxDistance = 0; + + TQ_UINT8 redThis = layerPointer[2]; + TQ_UINT8 greenThis = layerPointer[1]; + TQ_UINT8 blueThis = layerPointer[0]; + TQ_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; + TQ_UINT8 * pixelPos = layer.bits() + (y * width * 4) + (x * 4); + TQ_UINT8 red = *(pixelPos +2); + TQ_UINT8 green = *(pixelPos + 1); + TQ_UINT8 blue = *pixelPos; + TQ_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 = tqshapeStrategy->valueAt(sampleX, sampleY); + t = repeatStrategy->valueAt(t); + + if (reverseGradient) { + t = 1 - t; + } + + TQ_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, tqRgba(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 tqshapeStrategy; + + emit notifyProgressDone(); + + return !m_cancelRequested; +} diff --git a/chalk/core/kis_gradient_painter.h b/chalk/core/kis_gradient_painter.h new file mode 100644 index 00000000..fc31b117 --- /dev/null +++ b/chalk/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 tqshape, + enumGradientRepeat repeat, + double antiAliasThreshold, + bool reverseGradient, + TQ_INT32 startx, + TQ_INT32 starty, + TQ_INT32 width, + TQ_INT32 height); + + +private: + KisGradient *m_gradient; + + +}; +#endif //KIS_GRADIENT_PAINTER_H_ diff --git a/chalk/core/kis_group_layer.cc b/chalk/core/kis_group_layer.cc new file mode 100644 index 00000000..a3a35339 --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqdatetime.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 TQString &name, TQ_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 TQRect & 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 TQRect & rect) +{ + // We don't have a tqparent, 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 (tqparent() == 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 = TQRect(); So the non-intersecting part gets brilliantly lost otherwise. + const TQRect 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->tqparent().data() == this) + return layer->index(); + return -1; +} + +void KisGroupLayer::setIndex(KisLayerSP layer, int index) +{ + if (layer->tqparent().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->tqparent() || m_layers.tqcontains(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->tqparent().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 tqchildren, 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->tqparent().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); + } +} + +TQRect KisGroupLayer::extent() const +{ + TQRect groupExtent; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + groupExtent |= (*it)->extent(); + } + + return groupExtent; +} + +TQRect KisGroupLayer::exactBounds() const +{ + TQRect groupExactBounds; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + groupExactBounds |= (*it)->exactBounds(); + } + + return groupExactBounds; +} + +TQ_INT32 KisGroupLayer::x() const +{ + return m_x; +} + +void KisGroupLayer::setX(TQ_INT32 x) +{ + TQ_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; +} + +TQ_INT32 KisGroupLayer::y() const +{ + return m_y; +} + +void KisGroupLayer::setY(TQ_INT32 y) +{ + TQ_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; +} + +TQImage KisGroupLayer::createThumbnail(TQ_INT32 w, TQ_INT32 h) +{ + return m_projection->createThumbnail(w, h); +} + +void KisGroupLayer::updateProjection(const TQRect & 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 tqchildren, 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 = (tqparent() != 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 tqmask/selection into account, so we need + // to make a difference between a paintlayer with a tqmask, 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/chalk/core/kis_group_layer.h b/chalk/core/kis_group_layer.h new file mode 100644 index 00000000..3a2e042a --- /dev/null +++ b/chalk/core/kis_group_layer.h @@ -0,0 +1,141 @@ +/* + * 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 + TQ_OBJECT + +public: + KisGroupLayer(KisImage *img, const TQString &name, TQ_UINT8 opacity); + KisGroupLayer(const KisGroupLayer& rhs); + virtual ~KisGroupLayer(); + + virtual KisLayerSP clone() const; +public: + + /** + * Set the entire layer extent dirty; this percolates up to tqparent 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 tqparent layers all the way to the root + * layer. + */ + virtual void setDirty(const TQRect & rect, bool propagate = true); + + virtual void activate() {}; + + virtual void deactivate() {}; + + virtual TQ_INT32 x() const; + virtual void setX(TQ_INT32); + + virtual TQ_INT32 y() const; + virtual void setY(TQ_INT32); + + // Sets this layer and all its descendants' owner image to the given image. + virtual void setImage(KisImage *image); + + virtual TQRect extent() const; + virtual TQRect exactBounds() const; + + virtual bool accept(KisLayerVisitor &v) + { +// kdDebug(41001) << "GROUP\t\t" << name() +// << " dirty: " << dirty() +// << ", " << m_layers.count() << " tqchildren " +// << ", 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 TQRect & 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 TQImage createThumbnail(TQ_INT32 w, TQ_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(TQRect rc); + +private: + + void updateProjection(const TQRect & 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 + + TQ_INT32 m_x; + TQ_INT32 m_y; +}; + +#endif // KIS_GROUP_LAYER_H_ + diff --git a/chalk/core/kis_histogram.cc b/chalk/core/kis_histogram.cc new file mode 100644 index 00000000..bece7c9a --- /dev/null +++ b/chalk/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 <tqdatetime.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() +{ + TQ_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(); + + TQTime 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); +} + +TQValueVector<KisHistogram::Calculations> KisHistogram::calculateForRange(double from, double to) { + TQValueVector<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; + TQ_UINT32 high = 0, low = (TQ_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; + } + + TQ_INT32 totbins = m_producer->numberOfBins(); + TQ_UINT32 current; + + // convert the double range into actual bins: + double factor = static_cast<double>(totbins) / m_producer->viewWidth(); + + TQ_INT32 fromBin = static_cast<TQ_INT32>((from - m_producer->viewFrom()) * factor); + TQ_INT32 toBin = fromBin + static_cast<TQ_INT32>((to - from) * factor); + + // Min, max, count, low, high + for (TQ_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 " + << TQString().setNum(i) + << ": " + << TQString().setNum(m_values[i]) + << "\n"; + }*/ + kdDebug(DBG_AREA_MATH) << "\n"; + + kdDebug(DBG_AREA_MATH) << "Max: " << TQString(TQString().setNum(c.getMax())) << "\n"; + kdDebug(DBG_AREA_MATH) << "Min: " << TQString(TQString().setNum(c.getMin())) << "\n"; + kdDebug(DBG_AREA_MATH) << "High: " << TQString(TQString().setNum(c.getHighest())) << "\n"; + kdDebug(DBG_AREA_MATH) << "Low: " << TQString(TQString().setNum(c.getLowest())) << "\n"; + kdDebug(DBG_AREA_MATH) << "Mean: " << TQString(m_producer->positionToString(c.getMean())) << "\n"; + kdDebug(DBG_AREA_MATH) << "Total: " << TQString(TQString().setNum(c.getTotal())) << "\n"; +// kdDebug(DBG_AREA_MATH) << "Median: " << TQString().setNum(m_median) << "\n"; +// kdDebug(DBG_AREA_MATH) << "Stddev: " << TQString().setNum(m_stddev) << "\n"; +// kdDebug(DBG_AREA_MATH) << "percentile: " << TQString().setNum(m_percentile) << "\n"; + + kdDebug(DBG_AREA_MATH) << "\n"; +} diff --git a/chalk/core/kis_histogram.h b/chalk/core/kis_histogram.h new file mode 100644 index 00000000..3d315994 --- /dev/null +++ b/chalk/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; + + TQ_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 TQ_UINT32 getHighest() { return m_high; } + /// This function return the lowest value of the histogram + inline TQ_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 TQ_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; } + //TQ_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 TQ_UINT32 getValue(TQ_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(TQ_INT32 channel) { m_channel = channel; } + inline KisHistogramProducerSP producer() { return m_producer; } + inline TQ_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(); + TQValueVector<Calculations> calculateForRange(double from, double to); + Calculations calculateSingleRange(int channel, double from, double to); + + KisPaintDeviceSP m_device; + KisHistogramProducerSP m_producer; + + enumHistogramType m_type; + + TQ_INT32 m_channel; + double m_selFrom, m_selTo; + bool m_selection; + + KisPaintDeviceSP m_dev; + + TQValueVector<Calculations> m_completeCalculations, m_selectionCalculations; +}; + + +#endif // KIS_HISTOGRAM_WIDGET_ diff --git a/chalk/core/kis_image.cc b/chalk/core/kis_image.cc new file mode 100644 index 00000000..30f3ad36 --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqpainter.h> +#include <tqsize.h> +#include <tqtl.h> +#include <tqapplication.h> +#include <tqthread.h> +#include <tqdatetime.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; + TQ_UINT32 lockCount; + bool sizeChangedWhileLocked; + bool selectionChangedWhileLocked; + KisSubstrateSP substrate; + KisPerspectiveGrid* perspectiveGrid; +}; + + +namespace { + + class KisResizeImageCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisResizeImageCmd(KisUndoAdapter *adapter, + KisImageSP img, + TQ_INT32 width, + TQ_INT32 height, + TQ_INT32 oldWidth, + TQ_INT32 oldHeight) : super(i18n("Resize Image")) + { + m_adapter = adapter; + m_img = img; + m_before = TQSize(oldWidth, oldHeight); + m_after = TQSize(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; + TQSize m_before; + TQSize m_after; + }; + + // ------------------------------------------------------- + + class KisChangeLayersCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisChangeLayersCmd(KisUndoAdapter *adapter, KisImageSP img, + KisGroupLayerSP oldRootLayer, KisGroupLayerSP newRootLayer, const TQString& 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 TQString& name, KisImageSP image); + virtual ~KisImageCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisImageSP m_image; + }; + + KisImageCommand::KisImageCommand(const TQString& 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 TQString& name, KisImageSP image, KisLayerSP layer, KisGroupLayerSP tqparent, KisLayerSP aboveThis) : super(name, image) + { + m_layer = layer; + m_oldParent = layer->tqparent(); + m_oldAboveThis = layer->nextSibling(); + m_newParent = tqparent; + 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->tqparent(); + 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->tqparent(); + 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 TQString& name, + TQ_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() + { + TQString name = m_layer->name(); + TQ_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; + TQString m_name; + TQ_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, TQ_INT32 width, TQ_INT32 height, KisColorSpace * colorSpace, const TQString& name) + : TQObject(0, name.latin1()), KShared() +{ + init(adapter, width, height, colorSpace, name); + setName(name); + m_dcop = 0L; +} + +KisImage::KisImage(const KisImage& rhs) : TQObject(), 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 = TQString(); + 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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + + 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) { + TQString 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; +} + +TQString KisImage::name() const +{ + return m_name; +} + +void KisImage::setName(const TQString& name) +{ + if (!name.isEmpty()) + m_name = name; +} + +TQString KisImage::description() const +{ + return m_description; +} + +void KisImage::setDescription(const TQString& 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; +} + + +TQString 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, TQ_INT32 width, TQ_INT32 height, KisColorSpace * colorSpace, const TQString& 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(TQt::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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + + 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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + 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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + } + } +} + +void KisImage::emitSizeChanged() +{ + if (!locked()) { + emit sigSizeChanged(m_width, m_height); + } else { + m_private->sizeChangedWhileLocked = true; + } +} + +void KisImage::notifyLayerUpdated(KisLayerSP layer, TQRect rc) +{ + emit sigLayerUpdated(layer, rc); +} + +void KisImage::resize(TQ_INT32 w, TQ_INT32 h, TQ_INT32 x, TQ_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(TQRect(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 TQRect& 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? + TQ_INT32 w, h; + w = (TQ_INT32)(( width() * sx) + 0.5); + h = (TQ_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(); + + TQ_INT32 w = width(); + TQ_INT32 h = height(); + TQ_INT32 tx = TQ_INT32((w*cos(radians) - h*sin(radians) - w) / 2 + 0.5); + TQ_INT32 ty = TQ_INT32((h*cos(radians) + w*sin(radians) - h) / 2 + 0.5); + w = (TQ_INT32)(width()*TQABS(cos(radians)) + height()*TQABS(sin(radians)) + 0.5); + h = (TQ_INT32)(height()*TQABS(cos(radians)) + width()*TQABS(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 + TQ_INT32 w=width(); + TQ_INT32 h=height(); + + + if(angleX != 0 || angleY != 0){ + double deltaY=height()*TQABS(tan(angleX*pi/180)*tan(angleY*pi/180)); + w = (TQ_INT32) ( width() + TQABS(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 = (TQ_INT32) ( height() + TQABS(w*tan(angleY*pi/180)) ); + else if (angleX > 0 && angleY > 0) + h = (TQ_INT32) ( height() + TQABS(w*tan(angleY*pi/180))- 2 * deltaY + 2 ); + else if (angleX < 0 && angleY < 0) + h = (TQ_INT32) ( height() + TQABS(w*tan(angleY*pi/180))- 2 * deltaY + 2 ); + else + h = (TQ_INT32) ( height() + TQABS(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, TQ_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; +} + +TQ_INT32 KisImage::width() const +{ + return m_width; +} + +TQ_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 TQString& name, TQ_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->tqparent().data(), m_activeLayer->nextSibling()); + } + else { + addLayer(layer, m_rootLayer, 0); + } + activate(layer); + + return layer; +} + +void KisImage::setLayerProperties(KisLayerSP layer, TQ_UINT8 opacity, const KisCompositeOp& compositeOp, const TQString& name) +{ + if (layer && (layer->opacity() != opacity || layer->compositeOp() != compositeOp || layer->name() != name)) { + if (undo()) { + TQString oldname = layer->name(); + TQ_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(TQRect(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 TQString& name) const +{ + return rootLayer()->findLayer(name); +} + +KisLayerSP KisImage::findLayer(int id) const +{ + return rootLayer()->findLayer(id); +} + + +bool KisImage::addLayer(KisLayerSP layer, KisGroupLayerSP tqparent) +{ + return addLayer(layer, tqparent, tqparent->firstChild()); +} + +bool KisImage::addLayer(KisLayerSP layer, KisGroupLayerSP tqparent, KisLayerSP aboveThis) +{ + if (!tqparent) + return false; + + const bool success = tqparent->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! + TQValueVector<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, TQT_SIGNAL(sigMaskInfoChanged()), this, TQT_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 tqparent = layer->tqparent()) { + // 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) { + TQRect 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, TQT_SIGNAL(sigMaskInfoChanged()), + this, TQT_SIGNAL(sigMaskInfoChanged())); + } + KisLayerSP l = layer->prevSibling(); + TQRect 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 = tqparent->removeLayer(layer); + if (success) { + layer->setImage(0); + if (!layer->temporary() && undo()) { + m_adapter->addCommand(new LayerRmCmd(m_adapter, this, layer, tqparent, wasAbove)); + } + if (!layer->temporary()) { + emit sigLayerRemoved(layer, tqparent, wasAbove); + if (wasActive) { + if (wasBelow) + activate(wasBelow); + else if (wasAbove) + activate(wasAbove); + else if (tqparent != rootLayer()) + activate(tqparent.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->tqparent().data(), layer->prevSibling()); +} + +bool KisImage::lowerLayer(KisLayerSP layer) +{ + if (!layer) + return false; + if (KisLayerSP next = layer->nextSibling()) + return moveLayer(layer, layer->tqparent().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 tqparent, KisLayerSP aboveThis) +{ + if (!tqparent) + return false; + + KisGroupLayerSP wasParent = layer->tqparent(); + KisLayerSP wasAbove = layer->nextSibling(); + + if (wasParent.data() == tqparent.data() && wasAbove.data() == aboveThis.data()) + return false; + + lock(); + + if (!wasParent->removeLayer(layer)) { + unlock(); + return false; + } + + const bool success = tqparent->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; +} + +TQ_INT32 KisImage::nlayers() const +{ + return rootLayer()->numLayers() - 1; +} + +TQ_INT32 KisImage::nHiddenLayers() const +{ + return rootLayer()->numLayers(KisLayer::Hidden); +} + +void KisImage::flatten() +{ + KisGroupLayerSP oldRootLayer = m_rootLayer; + disconnect(oldRootLayer, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + + KisPaintLayer *dst = new KisPaintLayer(this, nextLayerName(), OPACITY_OPAQUE, colorSpace()); + Q_CHECK_PTR(dst); + + TQRect 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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + + 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); + + TQRect 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->tqparent(), layer); + removeLayer(layer); + + undoAdapter()->endMacro(); +} + + +void KisImage::setModified() +{ + emit sigImageModified(); +} + +void KisImage::renderToPainter(TQ_INT32 x1, + TQ_INT32 y1, + TQ_INT32 x2, + TQ_INT32 y2, + TQPainter &painter, + KisProfile * monitorProfile, + PaintFlags paintFlags, + float exposure) +{ + + TQImage img = convertToTQImage(x1, y1, x2, y2, monitorProfile, exposure); + + TQ_INT32 w = x2 - x1 + 1; + TQ_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); +} + +TQImage KisImage::convertToTQImage(TQ_INT32 x1, + TQ_INT32 y1, + TQ_INT32 x2, + TQ_INT32 y2, + KisProfile * profile, + float exposure) +{ + TQ_INT32 w = x2 - x1 + 1; + TQ_INT32 h = y2 - y1 + 1; + + KisPaintDeviceSP dev = m_rootLayer->projection(TQRect(x1, y1, w, h)); + TQImage img = dev->convertToTQImage(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 TQImage(); +} + +TQImage KisImage::convertToTQImage(const TQRect& r, const TQSize& scaledImageSize, KisProfile *profile, PaintFlags paintFlags, float exposure) +{ + + if (r.isEmpty() || scaledImageSize.isEmpty()) { + return TQImage(); + } + + TQ_INT32 imageWidth = width(); + TQ_INT32 imageHeight = height(); + TQ_UINT32 pixelSize = colorSpace()->pixelSize(); + + double xScale = static_cast<double>(imageWidth) / scaledImageSize.width(); + double yScale = static_cast<double>(imageHeight) / scaledImageSize.height(); + + TQRect 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); + TQTime t; + t.start(); + + TQ_UINT8 *scaledImageData = new TQ_UINT8[r.width() * r.height() * pixelSize]; + + TQ_UINT8 *imageRow = new TQ_UINT8[srcRect.width() * pixelSize]; + const TQ_INT32 imageRowX = srcRect.x(); + + for (TQ_INT32 y = 0; y < r.height(); ++y) { + + TQ_INT32 dstY = r.y() + y; + TQ_INT32 dstX = r.x(); + TQ_INT32 srcY = (dstY * imageHeight) / scaledImageSize.height(); + + mergedImage->readBytes(imageRow, imageRowX, srcY, srcRect.width(), 1); + + TQ_UINT8 *dstPixel = scaledImageData + (y * r.width() * pixelSize); + TQ_UINT32 columnsRemaining = r.width(); + + while (columnsRemaining > 0) { + + TQ_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; + + TQImage image = colorSpace()->convertToTQImage(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, TQSize(imageWidth, imageHeight)); + image.setAlphaBuffer(false); + } + + if (paintFlags & PAINT_SELECTION) { + if (m_activeLayer != 0) { + m_activeLayer->paintSelection(image, r, scaledImageSize, TQSize(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(TQRect(0, 0, m_width, m_height)); +} + +KisColor KisImage::mergedPixel(TQ_INT32 x, TQ_INT32 y) +{ + return m_rootLayer->projection(TQRect(x, y, 1, 1))->colorAt(x, y); +} + +void KisImage::notifyLayersChanged() +{ + emit sigLayersChanged(rootLayer()); +} + +void KisImage::notifyPropertyChanged(KisLayerSP layer) +{ + emit sigLayerPropertiesChanged(layer); +} + +void KisImage::notifyImageLoaded() +{ +} + +TQRect KisImage::bounds() const +{ + return TQRect(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 TQRect& r) +{ + TQRect 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, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + + m_rootLayer = rootLayer; + + if (!locked()) { + connect(m_rootLayer, TQT_SIGNAL(sigDirty(TQRect)), this, TQT_SIGNAL(sigImageUpdated(TQRect))); + } + 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(TQString type) +{ + vKisAnnotationSP_it it = m_annotations.begin(); + while (it != m_annotations.end()) { + if ((*it)->type() == type) { + return *it; + } + ++it; + } + return 0; +} + +void KisImage::removeAnnotation(TQString 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/chalk/core/kis_image.h b/chalk/core/kis_image.h new file mode 100644 index 00000000..4db23d3c --- /dev/null +++ b/chalk/core/kis_image.h @@ -0,0 +1,461 @@ +/* + * 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 <tqobject.h> +#include <tqstring.h> +#include <tqvaluevector.h> +#include <tqmutex.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 TQObject, public KShared { + Q_OBJECT + TQ_OBJECT + +public: + KisImage(KisUndoAdapter * adapter, TQ_INT32 width, TQ_INT32 height, KisColorSpace * colorSpace, const TQString& 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(TQ_INT32 x1, + TQ_INT32 y1, + TQ_INT32 x2, + TQ_INT32 y2, + TQPainter &painter, + KisProfile *profile, + PaintFlags paintFlags, + float exposure = 0.0f); + /** + * Render the projection onto a TQImage. In contrast with the above method, the + * selection is not rendered. + */ + virtual TQImage convertToTQImage(TQ_INT32 x1, + TQ_INT32 y1, + TQ_INT32 x2, + TQ_INT32 y2, + KisProfile * profile, + float exposure = 0.0f); + + virtual TQImage convertToTQImage(const TQRect& r, const TQSize& 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 Chalk 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); + + TQString name() const; + void setName(const TQString& name); + + TQString description() const; + void setDescription(const TQString& description); + + TQString nextLayerName() const; + void rollBackLayerName(); + + KisPerspectiveGrid* perspectiveGrid(); + void createPerspectiveGrid(TQPoint topLeft, TQPoint topRight, TQPoint bottomRight, TQPoint 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(TQ_INT32 w, TQ_INT32 h, TQ_INT32 x = 0, TQ_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 TQRect& 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, TQ_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); + + TQ_INT32 width() const; + TQ_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(TQ_INT32 x, TQ_INT32 y); + + /// Creates a new paint layer with the specified properties, adds it to the image, and returns it. + KisLayerSP newLayer(const TQString& name, TQ_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, TQ_UINT8 opacity, const KisCompositeOp& compositeOp, const TQString& 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 TQString& name) const; + KisLayerSP findLayer(int id) const; + + /// Move layer to specified position + bool moveLayer(KisLayerSP layer, KisGroupLayerSP tqparent, 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 tqparent the tqparent layer + */ + bool addLayer(KisLayerSP layer, KisGroupLayerSP tqparent); + + /** + * Add already existing layer to image. + * + * @param layer the layer to be added + * @param tqparent the tqparent layer + * @param aboveThis in the list with child layers of the specified + * tqparent, 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 tqparent, 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); + + TQ_INT32 nlayers() const; + TQ_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); + + TQRect bounds() const; + + /// use if the layers have changed _completely_ (eg. when flattening) + void notifyLayersChanged(); + + void notifyPropertyChanged(KisLayerSP layer); + + void notifyImageLoaded(); + + void notifyLayerUpdated(KisLayerSP layer, TQRect 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(TQString type); + + /** delete the annotation, if the image contains it */ + void removeAnnotation(TQString 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 TQRect& 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 tqparent(), 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 tqparent the tqparent 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 tqparent layer, or its tqparent changes. + + @param previousParent the tqparent 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(TQRect 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, TQRect rc); + + /** + * Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. + */ + void sigImageModified(); + + void sigSizeChanged(TQ_INT32 w, TQ_INT32 h); + void sigProfileChanged(KisProfile * profile); + void sigColorSpaceChanged(KisColorSpace* cs); + + + /// Emitted when any layer's tqmask info got updated (or when the current layer changes) + void sigMaskInfoChanged(); +public slots: + void slotSelectionChanged(); + void slotSelectionChanged(const TQRect& r); + + +private: + KisImage& operator=(const KisImage& rhs); + void init(KisUndoAdapter * adapter, TQ_INT32 width, TQ_INT32 height, KisColorSpace * colorSpace, const TQString& name); + void emitSizeChanged(); + +private: + + KURL m_uri; + TQString m_name; + TQString m_description; + + TQ_INT32 m_width; + TQ_INT32 m_height; + + double m_xres; + double m_yres; + + KoUnit::Unit m_unit; + + KisColorSpace * m_colorSpace; + + bool m_dirty; + TQRect 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/chalk/core/kis_image_iface.cc b/chalk/core/kis_image_iface.cc new file mode 100644 index 00000000..a52cc8a4 --- /dev/null +++ b/chalk/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 TQString& 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/chalk/core/kis_image_iface.h b/chalk/core/kis_image_iface.h new file mode 100644 index 00000000..acf36738 --- /dev/null +++ b/chalk/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 <tqstring.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 TQString& 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/chalk/core/kis_imagepipe_brush.cc b/chalk/core/kis_imagepipe_brush.cc new file mode 100644 index 00000000..5cef04d6 --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqpoint.h> +#include <tqvaluevector.h> +#include <tqfile.h> +#include <tqregexp.h> +#include <tqstringlist.h> +#include <tqtextstream.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 TQString& source) +{ + needsMovement = false; + TQRegExp basicSplitter(" ", true); + TQRegExp parasiteSplitter(":", true); + TQStringList parasites = TQStringList::split(basicSplitter, source); + for (uint i = 0; i < parasites.count(); i++) { + TQStringList splitted = TQStringList::split(parasiteSplitter, *parasites.at(i)); + if (splitted.count() != 2) { + kdWarning(41001) << "Wrong count for this parasite key/value:" << *parasites.at(i) << endl; + continue; + } + TQString 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) { + TQString 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(TQIODevice* dev) const { + // write out something like + // <count> ncells:<count> dim:<dim> rank0:<rank0> sel0:<sel0> <...> + + TQTextStream 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 TQString& filename) : super(filename) +{ + m_brushType = INVALID; + m_numOfBrushes = 0; + m_currentBrush = 0; +} + +KisImagePipeBrush::KisImagePipeBrush(const TQString& name, int w, int h, + TQValueVector< TQValueVector<KisPaintDevice*> > devices, + TQValueVector<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() +{ + TQFile 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 + TQValueVector<char> line1; + + TQ_UINT32 i = 0; + + while (m_data[i] != '\n' && i < m_data.size()) { + line1.append(m_data[i]); + i++; + } + setName(i18n(TQString::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. + TQValueVector<char> line2; + while (m_data[i] != '\n' && i < m_data.size()) { + line2.append(m_data[i]); + i++; + } + + TQString paramline = TQString::fromUtf8((&line2[0]), line2.size()); + TQ_UINT32 m_numOfBrushes = paramline.left(paramline.tqfind(' ')).toUInt(); + m_parasite = paramline.mid(paramline.tqfind(' ') + 1); + i++; // Skip past the second newline + + TQ_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() +{ + TQFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + bool ok = saveToDevice(TQT_TQIODEVICE(&file)); + file.close(); + return ok; +} + +bool KisImagePipeBrush::saveToDevice(TQIODevice* dev) const +{ + TQCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8 + char const* name = utf8Name.data(); + int len = tqstrlen(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; +} + +TQImage KisImagePipeBrush::img() +{ + if (m_brushes.isEmpty()) { + return 0; + } + else { + return m_brushes.at(0)->img(); + } +} + +KisAlphaMaskSP KisImagePipeBrush::tqmask(const KisPaintInformation& info, double subPixelX, double subPixelY) const +{ + if (m_brushes.isEmpty()) return 0; + selectNextBrush(info); + return m_brushes.at(m_currentBrush)->tqmask(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 TQString& 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... + TQValueVector< TQValueVector<KisPaintDevice*> > devices; + TQValueVector<KisPipeBrushParasite::SelectionMode> modes; + + devices.push_back(TQValueVector<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->convertFromTQImage(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/chalk/core/kis_imagepipe_brush.h b/chalk/core/kis_imagepipe_brush.h new file mode 100644 index 00000000..bc54204b --- /dev/null +++ b/chalk/core/kis_imagepipe_brush.h @@ -0,0 +1,169 @@ +/* + * 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 <tqptrlist.h> +#include <tqvaluelist.h> +#include <tqvaluevector.h> +#include <tqmap.h> +#include <tqstring.h> + +#include <kio/job.h> + +#include "kis_resource.h" +#include "kis_brush.h" +#include "kis_global.h" + +class TQCString; +class TQImage; +class TQPoint; +class TQSize; + +/** + * 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 TQString& source); + /** + * Saves a GIMP-compatible representation of this parasite to the device. Also writes the + * number of brushes (== ncells) (no trailing '\n') */ + bool saveToDevice(TQIODevice* 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; + //TQ_INT32 step; + TQ_INT32 ncells; + TQ_INT32 dim; + // Apparantly only used for editing a pipe brush, which we won't at the moment + // TQ_INT32 cols, rows; + // TQ_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; + TQ_INT32 rank[MaxDim]; + SelectionMode selection[MaxDim]; + /// The total count of brushes in each dimension (helper) + TQ_INT32 brushesCount[MaxDim]; + /// The current index in each dimension, so that the selection modes know where to start + TQ_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 + TQ_OBJECT + +public: + KisImagePipeBrush(const TQString& 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 TQString& name, int w, int h, + TQValueVector< TQValueVector<KisPaintDevice*> > devices, + TQValueVector<KisPipeBrushParasite::SelectionMode> modes); + virtual ~KisImagePipeBrush(); + + virtual bool load(); + virtual bool save(); + /// Will call KisBrush's saveToDevice as well + virtual bool saveToDevice(TQIODevice* dev) const; + + /** + @return the next image in the pipe. + */ + virtual TQImage img(); + + /** + @return the next tqmask in the pipe. + */ + virtual KisAlphaMaskSP tqmask(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 TQString& parasite); + void selectNextBrush(const KisPaintInformation& info) const; + + TQString m_name; + TQString m_parasiteString; // Contains instructions on how to use the brush + mutable KisPipeBrushParasite m_parasite; + TQ_UINT32 m_numOfBrushes; + mutable TQ_UINT32 m_currentBrush; + + TQByteArray m_data; + mutable TQPtrList<KisBrush> m_brushes; + + enumBrushType m_brushType; + +}; + +#endif // KIS_IMAGEPIPE_BRUSH_ diff --git a/chalk/core/kis_iterator.cc b/chalk/core/kis_iterator.cc new file mode 100644 index 00000000..0e0c2d81 --- /dev/null +++ b/chalk/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 <tqglobal.h> + +#include <kdebug.h> + +#include "kis_iterator.h" +#include "kis_datamanager.h" +#include "kis_tilediterator.h" + +KisRectIterator::KisRectIterator ( KisDataManager *dm, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_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() +{ +} + +TQ_UINT8 * KisRectIterator::rawData() const { return m_iter->rawData();} + +const TQ_UINT8 * KisRectIterator::oldRawData() const { return m_iter->oldRawData();} + +TQ_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(); } + +TQ_INT32 KisRectIterator::x() const { return m_iter->x(); } +TQ_INT32 KisRectIterator::y() const { return m_iter->y(); } + +//--------------------------------------------------------------------------------------- + +KisHLineIterator::KisHLineIterator ( KisDataManager *dm, TQ_INT32 x, TQ_INT32 y, TQ_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() +{ +} + +TQ_UINT8 *KisHLineIterator::rawData() const +{ + return m_iter->rawData(); +} + +const TQ_UINT8 *KisHLineIterator::oldRawData() const { return m_iter->oldRawData();} + +KisHLineIterator & KisHLineIterator::operator++() { m_iter->operator++(); return *this; } + +TQ_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(); } + +TQ_INT32 KisHLineIterator::x() const { return m_iter->x(); } + +TQ_INT32 KisHLineIterator::y() const { return m_iter->y(); } + +void KisHLineIterator::nextRow() { m_iter->nextRow(); } + +//--------------------------------------------------------------------------------------- + +KisVLineIterator::KisVLineIterator ( KisDataManager *dm, TQ_INT32 x, TQ_INT32 y, TQ_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() +{ +} + +TQ_UINT8 *KisVLineIterator::rawData() const { return m_iter->rawData();} + +const TQ_UINT8 * KisVLineIterator::oldRawData() const { return m_iter->oldRawData();} + +KisVLineIterator & KisVLineIterator::operator++() { m_iter->operator++(); return *this; } + +bool KisVLineIterator::isDone() const { return m_iter->isDone(); } + +TQ_INT32 KisVLineIterator::x() const { return m_iter->x(); } + +TQ_INT32 KisVLineIterator::y() const { return m_iter->y(); } + +void KisVLineIterator::nextCol() { return m_iter->nextCol(); } diff --git a/chalk/core/kis_iterator.h b/chalk/core/kis_iterator.h new file mode 100644 index 00000000..893461a5 --- /dev/null +++ b/chalk/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 <tqglobal.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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_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 + TQ_UINT8 * rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const TQ_UINT8 * oldRawData() const; + + /// Returns the number of consequtive pixels that we point at + /// This is useful for optimizing + TQ_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 + TQ_INT32 x() const; + + // current y position + TQ_INT32 y() const; + +private: + + KisTiledRectIteratorSP m_iter; +}; + +class KisHLineIterator +{ + +public: + + KisHLineIterator ( KisDataManager *dm, TQ_INT32 x, TQ_INT32 y, TQ_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 + TQ_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const TQ_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 + TQ_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 + TQ_INT32 x() const; + + /// current y position + TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_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 + TQ_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const TQ_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 + TQ_INT32 x() const; + + /// current y position + TQ_INT32 y() const; + + /// increment to the next column and rewind to the begining + void nextCol(); + +private: + + KisTiledVLineIteratorSP m_iter; + +}; + +#endif diff --git a/chalk/core/kis_iteratorpixeltrait.h b/chalk/core/kis_iteratorpixeltrait.h new file mode 100644 index 00000000..5d2c957f --- /dev/null +++ b/chalk/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 TQ_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 TQ_UINT8 selectedness() const + { + if (m_selectionIterator) + return *(m_selectionIterator->rawData()); + else { + return MAX_SELECTED; + } + }; + + /** + * Returns the selectiontqmask 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 TQ_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/chalk/core/kis_iterators_pixel.cc b/chalk/core/kis_iterators_pixel.cc new file mode 100644 index 00000000..3c5f1e44 --- /dev/null +++ b/chalk/core/kis_iterators_pixel.cc @@ -0,0 +1,59 @@ +/* + * This file is part of the Chalk 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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 offsetx, TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 h, TQ_INT32 offsetx, TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, TQ_INT32 offsetx, TQ_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/chalk/core/kis_iterators_pixel.h b/chalk/core/kis_iterators_pixel.h new file mode 100644 index 00000000..bd82f856 --- /dev/null +++ b/chalk/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, + TQ_INT32 x , TQ_INT32 y , TQ_INT32 w, TQ_INT32 offsetx, TQ_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; }; + + TQ_INT32 x() const { return KisHLineIterator::x() + m_offsetx; } + + TQ_INT32 y() const { return KisHLineIterator::y() + m_offsety; } + + TQ_INT32 nConseqHPixels() const { + if (m_selectionIterator) { + TQ_INT32 tqparent = KisHLineIteratorPixel::nConseqHPixels(); + TQ_INT32 selection = m_selectionIterator->nConseqHPixels(); + if (tqparent < selection) + return tqparent; + return selection; + } + return KisHLineIteratorPixel::nConseqHPixels(); + } +protected: + + TQ_INT32 m_offsetx, m_offsety; +}; + +class KisVLineIteratorPixel : public KisVLineIterator, public KisIteratorPixelTrait <KisVLineIterator> +{ +public: + KisVLineIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, + TQ_INT32 xpos , TQ_INT32 ypos , TQ_INT32 height, TQ_INT32 offsetx, TQ_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(); + } + + TQ_INT32 x() const { return KisVLineIterator::x() + m_offsetx; } + + TQ_INT32 y() const { return KisVLineIterator::y() + m_offsety; } + +protected: + + TQ_INT32 m_offsetx, m_offsety; +}; + +class KisRectIteratorPixel : public KisRectIterator, public KisIteratorPixelTrait <KisRectIterator> +{ +public: + KisRectIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, + TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, TQ_INT32 offsetx, TQ_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;} + + TQ_INT32 x() const { return KisRectIterator::x() + m_offsetx; } + + TQ_INT32 y() const { return KisRectIterator::y() + m_offsety; } + + TQ_INT32 nConseqPixels() const { + if (m_selectionIterator) { + TQ_INT32 tqparent = KisRectIterator::nConseqPixels(); + TQ_INT32 selection = m_selectionIterator->nConseqPixels(); + if (tqparent < selection) + return tqparent; + return selection; + } + return KisRectIterator::nConseqPixels(); + } + +protected: + + TQ_INT32 m_offsetx, m_offsety; +}; + +#endif diff --git a/chalk/core/kis_layer.cc b/chalk/core/kis_layer.cc new file mode 100644 index 00000000..cac178e5 --- /dev/null +++ b/chalk/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 <tqimage.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 TQString& name, KisLayerSP layer); + virtual ~KisLayerCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisLayerSP m_layer; + }; + + KisLayerCommand::KisLayerCommand(const TQString& 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, TQ_UINT8 oldOpacity, TQ_UINT8 newOpacity); + + virtual void execute(); + virtual void unexecute(); + + private: + TQ_UINT8 m_oldOpacity; + TQ_UINT8 m_newOpacity; + }; + + KisLayerOpacityCommand::KisLayerOpacityCommand(KisLayerSP layer, TQ_UINT8 oldOpacity, TQ_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 TQPoint& oldpos, const TQPoint& newpos); + virtual ~KisLayerOffsetCommand(); + + virtual void execute(); + virtual void unexecute(); + + private: + void moveTo(const TQPoint& pos); + + private: + KisLayerSP m_layer; + TQRect m_updateRect; + TQPoint m_oldPos; + TQPoint m_newPos; + }; + + KisLayerOffsetCommand::KisLayerOffsetCommand(KisLayerSP layer, const TQPoint& oldpos, const TQPoint& newpos) : + super(i18n("Move Layer")) + { + m_layer = layer; + m_oldPos = oldpos; + m_newPos = newpos; + + TQRect currentBounds = m_layer->exactBounds(); + TQRect 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 TQPoint& 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 TQString &name, TQ_UINT8 opacity) : + TQObject(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) : + TQObject(), + 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 TQRect & 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.tqcontains(m_dirtyRect)) m_dirtyRect = TQRect(); + m_dirtyRect = TQRect(); + } + +} + +bool KisLayer::dirty() +{ + return m_dirtyRect.isValid(); +} + + +bool KisLayer::dirty(const TQRect & rc) +{ + if (!m_dirtyRect.isValid() || !rc.isValid()) return false; + + return rc.intersects(m_dirtyRect); +} + +TQRect KisLayer::dirtyRect() const +{ + return m_dirtyRect; +} + +void KisLayer::setDirty(bool propagate) +{ + TQRect rc = extent(); + + if (rc.isValid()) m_dirtyRect = rc; + + // If we're dirty, our tqparent is dirty, if we've got a tqparent + 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 TQRect & rc, bool propagate) +{ + // If we're dirty, our tqparent is dirty, if we've got a tqparent + + 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::tqparent() const +{ + return m_parent; +} + +KisLayerSP KisLayer::prevSibling() const +{ + if (!tqparent()) + return 0; + return tqparent()->at(index() - 1); +} + +KisLayerSP KisLayer::nextSibling() const +{ + if (!tqparent()) + return 0; + return tqparent()->at(index() + 1); +} + +int KisLayer::index() const +{ + return m_index; +} + +void KisLayer::setIndex(int i) +{ + if (!tqparent()) + return; + tqparent()->setIndex(this, i); +} + +KisLayerSP KisLayer::findLayer(const TQString& 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; +} + +TQ_UINT8 KisLayer::opacity() const +{ + return m_opacity; +} + +void KisLayer::setOpacity(TQ_UINT8 val) +{ + if (m_opacity != val) + { + m_opacity = val; + setDirty(); + notifyPropertyChanged(); + } +} + +KNamedCommand *KisLayer::setOpacityCommand(TQ_UINT8 newOpacity) +{ + return new KisLayerOpacityCommand(this, opacity(), newOpacity); +} + +KNamedCommand *KisLayer::setOpacityCommand(TQ_UINT8 prevOpacity, TQ_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); +} + +TQString KisLayer::name() const +{ + return m_name; +} + +void KisLayer::setName(const TQString& 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(TQPoint oldPosition, TQPoint newPosition) +{ + return new KisLayerOffsetCommand(this, oldPosition, newPosition); +} + +KisUndoAdapter *KisLayer::undoAdapter() const +{ + if (m_image) { + return m_image->undoAdapter(); + } + return 0; +} + +void KisLayer::paintMaskInactiveLayers(TQImage &, TQ_INT32, TQ_INT32, TQ_INT32, TQ_INT32) +{ +} + +void KisLayer::paintSelection(TQImage &, TQ_INT32, TQ_INT32, TQ_INT32, TQ_INT32) +{ +} + +void KisLayer::paintSelection(TQImage &, const TQRect&, const TQSize&, const TQSize&) +{ +} + +TQImage KisLayer::createThumbnail(TQ_INT32, TQ_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(TQ_UINT8 o) { + m_compositeOpacity = o; +} + +KisPaintDeviceSP KisLayerSupportsIndirectPainting::temporaryTarget() { + return m_temporaryTarget; +} + +KisCompositeOp KisLayerSupportsIndirectPainting::temporaryCompositeOp() const { + return m_compositeOp; +} + +TQ_UINT8 KisLayerSupportsIndirectPainting::temporaryOpacity() const { + return m_compositeOpacity; +} + +#include "kis_layer.moc" diff --git a/chalk/core/kis_layer.h b/chalk/core/kis_layer.h new file mode 100644 index 00000000..09ecd235 --- /dev/null +++ b/chalk/core/kis_layer.h @@ -0,0 +1,257 @@ +/* + * 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 <tqobject.h> +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_composite_op.h" +#include <koffice_export.h> + +class KNamedCommand; +class TQPainter; +class KisUndoAdapter; +class KisGroupLayer; + +/** + * Abstract class that represents the concept of a Layer in Chalk. 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 TQObject, public KShared +{ + Q_OBJECT + TQ_OBJECT + +public: + KisLayer(KisImage *img, const TQString &name, TQ_UINT8 opacity); + KisLayer(const KisLayer& rhs); + virtual ~KisLayer(); + + + /** + * Set the specified rect to clean + */ + virtual void setClean(const TQRect & 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 TQRect & rc); + + + virtual TQRect dirtyRect() const; + + + /** + * Set the entire layer extent dirty; this percolates up to tqparent 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 tqparent layers all the way to the root + * layer. + */ + virtual void setDirty(const TQRect & 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 tqparent'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 tqparent's list of child layers. + virtual void setIndex(int index); + + /** + * Returns the tqparent layer of a layer. This is 0 only for a root layer; otherwise + * this will be an actual GroupLayer */ + virtual KisGroupLayerSP tqparent() const; + + /** + * Returns the previous sibling of this layer in the tqparent's list. This is the layer + * *above* this layer. 0 is returned if there is no tqparent, or if this child has no more + * previous siblings (== firstChild()) + */ + virtual KisLayerSP prevSibling() const; + + /** + * Returns the next sibling of this layer in the tqparent's list. This is the layer *below* + * this layer. 0 is returned if there is no tqparent, or if this child has no more next + * siblings (== lastChild()) + */ + virtual KisLayerSP nextSibling() const; + + /** + * Returns the sibling above this layer in its tqparent's list. 0 is returned if there is no tqparent, + * 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 tqparent's list. 0 is returned if there is no tqparent, + * 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 TQString& 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 TQ_INT32 x() const = 0; + virtual void setX(TQ_INT32) = 0; + + virtual TQ_INT32 y() const = 0; + virtual void setY(TQ_INT32) = 0; + + virtual KNamedCommand *moveCommand(TQPoint oldPosition, TQPoint newPosition); + + /// Returns an approximation of where the bounds on actual data are in this layer + virtual TQRect extent() const = 0; + /// Returns the exact bounds of where the actual data resides in this layer + virtual TQRect exactBounds() const = 0; + + virtual const bool visible() const; + virtual void setVisible(bool v); + KNamedCommand *setVisibleCommand(bool visiblel); + + TQ_UINT8 opacity() const; + void setOpacity(TQ_UINT8 val); + KNamedCommand *setOpacityCommand(TQ_UINT8 val); + KNamedCommand *setOpacityCommand(TQ_UINT8 prevOpacity, TQ_UINT8 newOpacity); + + bool locked() const; + void setLocked(bool l); + KNamedCommand *setLockedCommand(bool locked); + + void notifyPropertyChanged(); + + bool temporary() const; + void setTemporary(bool t); + + virtual TQString name() const; + virtual void setName(const TQString& 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 tqmask where the selection on this layer resides + virtual void paintSelection(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + virtual void paintSelection(TQImage &img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& 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(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + /// Returns a thumbnail in requested size. The TQImage may have transparent parts. + /// May also return 0 + virtual TQImage createThumbnail(TQ_INT32 w, TQ_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; + TQ_UINT8 m_opacity; + bool m_locked; + bool m_visible; + bool m_temporary; + + // XXX: keep a list of dirty rects instead of always aggegrating them + TQRect m_dirtyRect; + TQString 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; + TQ_UINT8 m_compositeOpacity; +public: + // Indirect painting + void setTemporaryTarget(KisPaintDeviceSP t); + void setTemporaryCompositeOp(const KisCompositeOp& c); + void setTemporaryOpacity(TQ_UINT8 o); + KisPaintDeviceSP temporaryTarget(); + KisCompositeOp temporaryCompositeOp() const; + TQ_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/chalk/core/kis_layer_visitor.h b/chalk/core/kis_layer_visitor.h new file mode 100644 index 00000000..4326bc54 --- /dev/null +++ b/chalk/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/chalk/core/kis_math_toolbox.cpp b/chalk/core/kis_math_toolbox.cpp new file mode 100644 index 00000000..f4fdf88c --- /dev/null +++ b/chalk/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(TQ_UINT8* data, int channelpos ) +{ + return (float)( *((T*)(data + channelpos)) ); +} + +typedef double (*PtrToDouble)(TQ_UINT8*, int); + +template<typename T> +void fromDouble(TQ_UINT8* data, int channelpos, double v ) +{ + *((T*)(data + channelpos)) = (T)v; +} + +typedef void (*PtrFromDouble)(TQ_UINT8*, int, double); + + +void KisMathToolbox::transformToFR(KisPaintDeviceSP src, KisFloatRepresentation* fr, const TQRect& rect) +{ + TQ_INT32 depth = src->colorSpace()->nColorChannels(); + TQMemArray<PtrToDouble> f(depth); + TQValueVector<KisChannelInfo *> cis = src->colorSpace()->channels(); + for(TQ_INT32 k = 0; k < depth; k++) + { + switch( cis[k]->channelValueType() ) + { + case KisChannelInfo::UINT8: + f[k] = toDouble<TQ_UINT8>; + break; + case KisChannelInfo::UINT16: + f[k] = toDouble<TQ_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<TQ_INT8>; + break; + case KisChannelInfo::INT16: + f[k] = toDouble<TQ_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() ) + { + TQ_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 TQRect& rect) +{ + TQ_INT32 depth = dst->colorSpace()->nColorChannels(); + TQMemArray<PtrFromDouble> f(depth); + TQValueVector<KisChannelInfo *> cis = dst->colorSpace()->channels(); + for(TQ_INT32 k = 0; k < depth; k++) + { + switch( cis[k]->channelValueType() ) + { + case KisChannelInfo::UINT8: + f[k] = fromDouble<TQ_UINT8>; + break; + case KisChannelInfo::UINT16: + f[k] = fromDouble<TQ_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<TQ_INT8>; + break; + case KisChannelInfo::INT16: + f[k] = fromDouble<TQ_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() ) + { + TQ_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/chalk/core/kis_math_toolbox.h b/chalk/core/kis_math_toolbox.h new file mode 100644 index 00000000..8d5bef50 --- /dev/null +++ b/chalk/core/kis_math_toolbox.h @@ -0,0 +1,124 @@ +/* + * 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 <tqobject.h> + +// typedef unsigned int uint; + +#include <kis_generic_registry.h> +#include "kis_paint_device.h" +#include "kis_types.h" + +#include <new> + +class KisMathToolbox : public TQObject { + Q_OBJECT + TQ_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 (TQ_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 TQRect&) throw(std::bad_alloc ); + inline uint fastWaveletTotalSteps(const TQRect&); + /** + * 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 TQRect&, 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 TQRect&, 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 TQRect&); + /** + * 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 TQRect&); + private: + KisID m_id; +}; + +class KisMathToolboxFactoryRegistry : public KisGenericRegistry<KisMathToolbox*> { + public: + KisMathToolboxFactoryRegistry(); + ~KisMathToolboxFactoryRegistry(); +}; + + +inline KisMathToolbox::KisWavelet* KisMathToolbox::initWavelet(KisPaintDeviceSP src, const TQRect& rect) throw(std::bad_alloc ) +{ + int size; + int maxrectsize = (rect.height() < rect.width()) ? rect.width() : rect.height(); + for(size = 2; size < maxrectsize; size *= 2) ; + TQ_INT32 depth = src->colorSpace()->nColorChannels(); + return new KisWavelet(size, depth); +} + +inline uint KisMathToolbox::fastWaveletTotalSteps(const TQRect& 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/chalk/core/kis_merge_visitor.h b/chalk/core/kis_merge_visitor.h new file mode 100644 index 00000000..68053384 --- /dev/null +++ b/chalk/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 <tqrect.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 TQRect& 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, + TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 dx, TQ_INT32 dy, + TQ_INT32 w, TQ_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; + + TQ_INT32 sx, sy, dx, dy, w, h; + + TQRect 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 tqmask, 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 tqmask 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 tqmask might not be extended + // enough. Check if that is the case + KisPaintDeviceSP tqmask = layer->getMask(); + TQRect mextent = tqmask->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 = tqmask->createRectIterator(sx, sy, w, h, true); + while (!it.isDone()) + ++it; + } + if (tempTarget) { + KisPaintDeviceSP temp = new KisPaintDevice(source->colorSpace()); + tqmask = paintIndirect(tqmask, temp, layer, sx, sy, dx, dy, w, h); + } + + gc.bitBlt(dx, dy, COMPOSITE_OVER, tqmask, OPACITY_OPAQUE, sx, sy, w, h); + } else { + KisSelectionSP tqmask = layer->getMaskAsSelection(); + // The indirect painting happens on the tqmask + if (tempTarget && layer->editMask()) { + KisPaintDeviceSP tqmaskSrc = layer->getMask(); + KisPaintDeviceSP temp = new KisPaintDevice(tqmaskSrc->colorSpace()); + temp = paintIndirect(tqmaskSrc, temp, layer, sx, sy, dx, dy, w, h); + // Blegh + KisRectIteratorPixel srcIt = temp->createRectIterator(sx, sy, w, h, false); + KisRectIteratorPixel dstIt = tqmask->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 tqmask, and paint indirect, but not on the tqmask + KisPaintDeviceSP temp = new KisPaintDevice(source->colorSpace()); + source = paintIndirect(source, temp, layer, sx, sy, dx, dy, w, h); + } + + gc.bltSelection(dx, dy, + layer->compositeOp(), + source, + tqmask, + 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; + + TQ_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); + TQRect 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; + + TQ_INT32 sx, sy, dx, dy, w, h; + + TQRect 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); + TQRect 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; + TQRect m_rc; +}; + +#endif // KIS_MERGE_H_ + diff --git a/chalk/core/kis_meta_registry.cc b/chalk/core/kis_meta_registry.cc new file mode 100644 index 00000000..c6025869 --- /dev/null +++ b/chalk/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 <tqstringlist.h> +#include <tqdir.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") + "chalk/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") + "chalk/profiles/"); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", "/usr/share/color/icc"); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", TQDir::homeDirPath() + TQString("/.icc/")); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", TQDir::homeDirPath() + TQString("/.color/icc/")); + + TQStringList 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/chalk/core/kis_meta_registry.h b/chalk/core/kis_meta_registry.h new file mode 100644 index 00000000..42aeee3d --- /dev/null +++ b/chalk/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/chalk/core/kis_nameserver.cc b/chalk/core/kis_nameserver.cc new file mode 100644 index 00000000..ff66144d --- /dev/null +++ b/chalk/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 TQString& prefix, TQ_INT32 seed) +{ + m_prefix = prefix; + m_generator = seed; +} + +KisNameServer::~KisNameServer() +{ +} + +TQString KisNameServer::name() +{ + return m_prefix.tqarg(m_generator++); +} + +TQ_INT32 KisNameServer::currentSeed() const +{ + return m_generator; +} + +TQ_INT32 KisNameServer::number() +{ + return m_generator++; +} + +void KisNameServer::rollback() +{ + m_generator--; +} + diff --git a/chalk/core/kis_nameserver.h b/chalk/core/kis_nameserver.h new file mode 100644 index 00000000..a6989853 --- /dev/null +++ b/chalk/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 <tqstring.h> +#include "kis_global.h" + +class KisNameServer { +public: + KisNameServer(const TQString& prefix, TQ_INT32 seed = 1); + ~KisNameServer(); + + TQString name(); + TQ_INT32 number(); + TQ_INT32 currentSeed() const; + void rollback(); + +private: + TQ_INT32 m_generator; + TQString m_prefix; +}; + +#endif // KIS_NAMESERVER_H_ + diff --git a/chalk/core/kis_paint_device.cc b/chalk/core/kis_paint_device.cc new file mode 100644 index 00000000..1ce2e98f --- /dev/null +++ b/chalk/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 <tqrect.h> +#include <tqwmatrix.h> +#include <tqimage.h> +#include <tqdatetime.h> +#include <tqapplication.h> +#include <tqvaluelist.h> +#include <tqtimer.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 TQString& name, KisPaintDeviceSP paintDevice); + virtual ~KisPaintDeviceCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisPaintDeviceSP m_paintDevice; + }; + + KisPaintDeviceCommand::KisPaintDeviceCommand(const TQString& 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 TQPoint& oldpos, const TQPoint& newpos); + virtual ~MoveCommand(); + + virtual void execute(); + virtual void unexecute(); + + private: + void moveTo(const TQPoint& pos); + void undoOff(); + void undoOn(); + + private: + KisPaintDeviceSP m_device; + TQPoint m_oldPos; + TQPoint m_newPos; + }; + + MoveCommand::MoveCommand(KisPaintDeviceSP device, const TQPoint& oldpos, const TQPoint& 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 TQPoint& 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) : + TQObject(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(); + + TQ_UINT8* defPixel = new TQ_UINT8 [ m_pixelSize ]; + colorSpace->fromTQColor(TQt::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 *tqparent, KisColorSpace * colorSpace, const char * name) : + TQObject(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 = tqparent; + + if (colorSpace == 0 && tqparent != 0 && tqparent->image() != 0) { + m_colorSpace = tqparent->image()->colorSpace(); + } + else { + m_colorSpace = colorSpace; + } + + Q_ASSERT( m_colorSpace ); + + m_pixelSize = m_colorSpace->pixelSize(); + m_nChannels = m_colorSpace->nChannels(); + + TQ_UINT8* defPixel = new TQ_UINT8[ m_pixelSize ]; + m_colorSpace->fromTQColor(TQt::black, OPACITY_TRANSPARENT, defPixel); + + m_datamanager = new KisDataManager(m_pixelSize, defPixel); + delete [] defPixel; + Q_CHECK_PTR(m_datamanager); + m_extentIsValid = true; + + if ( TQString ( name ) == TQString( "Layer 1" ) ) { + m_longRunningFilters = m_colorSpace->createBackgroundFilters(); + + if (!m_longRunningFilters.isEmpty()) { + m_longRunningFilterTimer = new TQTimer(this); + connect(m_longRunningFilterTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(runBackgroundFilters())); + m_longRunningFilterTimer->start(2000); + } + } +} + + +KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs) : TQObject(), 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; + TQValueList<KisFilter*>::iterator it; + TQValueList<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::tqparentLayer() const +{ + return m_parentLayer; +} + +void KisPaintDevice::setParentLayer(KisLayer *tqparentLayer) +{ + m_parentLayer = tqparentLayer; +} + +void KisPaintDevice::setDirty(const TQRect & 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(TQ_INT32 x, TQ_INT32 y) +{ + TQRect 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 TQPoint& pt) +{ + move(pt.x(), pt.y()); +} + +KNamedCommand * KisPaintDevice::moveCommand(TQ_INT32 x, TQ_INT32 y) +{ + KNamedCommand * cmd = new MoveCommand(this, TQPoint(m_x, m_y), TQPoint(x, y)); + Q_CHECK_PTR(cmd); + cmd->execute(); + return cmd; +} + +void KisPaintDevice::extent(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const +{ + m_datamanager->extent(x, y, w, h); + x += m_x; + y += m_y; +} + +TQRect KisPaintDevice::extent() const +{ + TQ_INT32 x, y, w, h; + extent(x, y, w, h); + return TQRect(x, y, w, h); +} + +bool KisPaintDevice::extentIsValid() const +{ + return m_extentIsValid; +} + +void KisPaintDevice::setExtentIsValid(bool isValid) +{ + m_extentIsValid = isValid; +} + +void KisPaintDevice::exactBounds(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const +{ + TQRect r = exactBounds(); + x = r.x(); + y = r.y(); + w = r.width(); + h = r.height(); +} + +TQRect KisPaintDevice::exactBoundsOldMethod() const +{ + TQ_INT32 x, y, w, h, boundX, boundY, boundW, boundH; + extent(x, y, w, h); + + extent(boundX, boundY, boundW, boundH); + + const TQ_UINT8* defaultPixel = m_datamanager->defaultPixel(); + + bool found = false; + + for (TQ_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 (TQ_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 (TQ_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 (TQ_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 TQRect(boundX, boundY, boundW, boundH); +} + +TQRect KisPaintDevice::exactBoundsImprovedOldMethod() const +{ + // Solution n°2 + TQ_INT32 x, y, w, h, boundX2, boundY2, boundW2, boundH2; + extent(x, y, w, h); + extent(boundX2, boundY2, boundW2, boundH2); + + const TQ_UINT8* defaultPixel = m_datamanager->defaultPixel(); + bool found = false; + { + KisHLineIterator it = const_cast<KisPaintDevice *>(this)->createHLineIterator(x, y, w, false); + for (TQ_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 (TQ_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 (TQ_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 (TQ_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 TQRect(boundX2, boundY2, boundW2, boundH2); +} + + +TQRect KisPaintDevice::exactBounds() const +{ + TQRect r2 = exactBoundsImprovedOldMethod(); + return r2; +} + +void KisPaintDevice::crop(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + m_datamanager->setExtent(x - m_x, y - m_y, w, h); +} + + +void KisPaintDevice::crop(TQRect r) +{ + r.moveBy(-m_x, -m_y); m_datamanager->setExtent(r); +} + +void KisPaintDevice::clear() +{ + m_datamanager->clear(); +} + +void KisPaintDevice::fill(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const TQ_UINT8 *fillPixel) +{ + m_datamanager->clear(x, y, w, h, fillPixel); +} + +void KisPaintDevice::mirrorX() +{ + TQRect r; + if (hasSelection()) { + r = selection()->selectedRect(); + } + else { + r = exactBounds(); + } + + for (TQ_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 */ + TQRect r; + if (hasSelection()) { + r = selection()->selectedRect(); + } + else { + r = exactBounds(); + } + + + TQ_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, TQ_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()); + + TQ_INT32 x, y, w, h; + extent(x, y, w, h); + + for (TQ_INT32 row = y; row < y + h; ++row) { + + TQ_INT32 column = x; + TQ_INT32 columnsRemaining = w; + + while (columnsRemaining > 0) { + + TQ_INT32 numContiguousDstColumns = dst.numContiguousColumns(column, row, row); + TQ_INT32 numContiguousSrcColumns = numContiguousColumns(column, row, row); + + TQ_INT32 columns = TQMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = TQMIN(columns, columnsRemaining); + + //const TQ_UINT8 *srcData = pixel(column, row); + //TQ_UINT8 *dstData = dst.writablePixel(column, row); + KisHLineIteratorPixel srcIt = createHLineIterator(column, row, columns, false); + KisHLineIteratorPixel dstIt = dst.createHLineIterator(column, row, columns, true); + + const TQ_UINT8 *srcData = srcIt.rawData(); + TQ_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::convertFromTQImage(const TQImage& image, const TQString &srcProfileName, + TQ_INT32 offsetX, TQ_INT32 offsetY) +{ + TQImage img = image; + + // Chalk is little-endian inside. + if (img.bitOrder() == TQImage::LittleEndian) { + img = img.convertBitOrder(TQImage::BigEndian); + } + kdDebug() << k_funcinfo << img.bitOrder()<< endl; + // Chalk 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 + TQ_UINT8 * dstData = new TQ_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()); +// } +} + +TQImage KisPaintDevice::convertToTQImage(KisProfile * dstProfile, float exposure) +{ + TQ_INT32 x1; + TQ_INT32 y1; + TQ_INT32 w; + TQ_INT32 h; + + x1 = - getX(); + y1 = - getY(); + + if (image()) { + w = image()->width(); + h = image()->height(); + } + else { + extent(x1, y1, w, h); + } + + return convertToTQImage(dstProfile, x1, y1, w, h, exposure); +} + +// XXX: is this faster than building the TQImage ourselves? It makes +TQImage KisPaintDevice::convertToTQImage(KisProfile * dstProfile, TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, float exposure) +{ + if (w < 0) + return TQImage(); + + if (h < 0) + return TQImage(); + + TQ_UINT8 * data = new TQ_UINT8 [w * h * m_pixelSize]; + Q_CHECK_PTR(data); + + // XXX: Is this really faster than converting line by line and building the TQImage directly? + // This copies potentially a lot of data. + readBytes(data, x1, y1, w, h); + TQImage image = colorSpace()->convertToTQImage(data, w, h, dstProfile, INTENT_PERCEPTUAL, exposure); + delete[] data; + + return image; +} + +KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(TQ_INT32 w, TQ_INT32 h) +{ + KisPaintDeviceSP thumbnail = new KisPaintDevice(colorSpace(), "thumbnail"); + + thumbnail->clear(); + + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const TQRect e = exactBounds(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = TQ_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = TQ_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = TQ_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = TQ_INT32(double(srcw) / srch * h); + + for (TQ_INT32 y=0; y < h; ++y) { + TQ_INT32 iY = (y * srch ) / h; + for (TQ_INT32 x=0; x < w; ++x) { + TQ_INT32 iX = (x * srcw ) / w; + thumbnail->setPixel(x, y, colorAt(iX, iY)); + } + } + + return thumbnail; + +} + + +TQImage KisPaintDevice::createThumbnail(TQ_INT32 w, TQ_INT32 h) +{ + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const TQRect e = extent(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = TQ_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = TQ_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = TQ_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = TQ_INT32(double(srcw) / srch * h); + + TQColor c; + TQ_UINT8 opacity; + TQImage img(w,h,32); + + for (TQ_INT32 y=0; y < h; ++y) { + TQ_INT32 iY = (y * srch ) / h; + for (TQ_INT32 x=0; x < w; ++x) { + TQ_INT32 iX = (x * srcw ) / w; + pixel(iX, iY, &c, &opacity); + const TQRgb rgb = c.rgb(); + img.setPixel(x, y, tqRgba(tqRed(rgb), tqGreen(rgb), tqBlue(rgb), opacity)); + } + } + + return img; +} + +KisRectIteratorPixel KisPaintDevice::createRectIterator(TQ_INT32 left, TQ_INT32 top, TQ_INT32 w, TQ_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(TQ_INT32 x, TQ_INT32 y, TQ_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(TQ_INT32 x, TQ_INT32 y, TQ_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(TQ_INT32 x, TQ_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 TQRect& 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()); + TQRect 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(); + + TQRect 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; + + TQRect r = m_selection->selectedExactRect(); + + if (r.isValid()) { + + for (TQ_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 tqmask) +{ + TQRect r = tqmask->selectedRect(); + crop(r); + + for (TQ_INT32 y = r.top(); y <= r.bottom(); ++y) { + + KisHLineIterator pixelIt = createHLineIterator(r.x(), y, r.width(), true); + KisHLineIterator tqmaskIt = tqmask->createHLineIterator(r.x(), y, r.width(), false); + + while (!pixelIt.isDone()) { + // XXX: Optimize by using stretches + + m_colorSpace->applyAlphaU8Mask( pixelIt.rawData(), tqmaskIt.rawData(), 1); + + ++pixelIt; + ++tqmaskIt; + } + } +} + +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(TQ_INT32 x, TQ_INT32 y, TQColor *c, TQ_UINT8 *opacity) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + TQ_UINT8 *pix = iter.rawData(); + + if (!pix) return false; + + colorSpace()->toTQColor(pix, c, opacity); + + return true; +} + + +bool KisPaintDevice::pixel(TQ_INT32 x, TQ_INT32 y, KisColor * kc) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + TQ_UINT8 *pix = iter.rawData(); + + if (!pix) return false; + + kc->setColor(pix, m_colorSpace); + + return true; +} + +KisColor KisPaintDevice::colorAt(TQ_INT32 x, TQ_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(TQ_INT32 x, TQ_INT32 y, const TQColor& c, TQ_UINT8 opacity) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + + colorSpace()->fromTQColor(c, opacity, iter.rawData()); + + return true; +} + +bool KisPaintDevice::setPixel(TQ_INT32 x, TQ_INT32 y, const KisColor& kc) +{ + TQ_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; +} + + +TQ_INT32 KisPaintDevice::numContiguousColumns(TQ_INT32 x, TQ_INT32 minY, TQ_INT32 maxY) +{ + return m_datamanager->numContiguousColumns(x - m_x, minY - m_y, maxY - m_y); +} + +TQ_INT32 KisPaintDevice::numContiguousRows(TQ_INT32 y, TQ_INT32 minX, TQ_INT32 maxX) +{ + return m_datamanager->numContiguousRows(y - m_y, minX - m_x, maxX - m_x); +} + +TQ_INT32 KisPaintDevice::rowStride(TQ_INT32 x, TQ_INT32 y) +{ + return m_datamanager->rowStride(x - m_x, y - m_y); +} + +const TQ_UINT8* KisPaintDevice::pixel(TQ_INT32 x, TQ_INT32 y) +{ + return m_datamanager->pixel(x - m_x, y - m_y); +} + +TQ_UINT8* KisPaintDevice::writablePixel(TQ_INT32 x, TQ_INT32 y) +{ + return m_datamanager->writablePixel(x - m_x, y - m_y); +} + +void KisPaintDevice::setX(TQ_INT32 x) +{ + m_x = x; + if(m_selection && m_selection != this) + m_selection->setX(x); +} + +void KisPaintDevice::setY(TQ_INT32 y) +{ + m_y = y; + if(m_selection && m_selection != this) + m_selection->setY(y); +} + + +void KisPaintDevice::readBytes(TQ_UINT8 * data, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + m_datamanager->readBytes(data, x - m_x, y - m_y, w, h); +} + +void KisPaintDevice::writeBytes(const TQ_UINT8 * data, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_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); + + TQRect rc = extent(); + if (!m_longRunningFilters.isEmpty()) { + TQValueList<KisFilter*>::iterator it; + TQValueList<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/chalk/core/kis_paint_device.h b/chalk/core/kis_paint_device.h new file mode 100644 index 00000000..6e804afe --- /dev/null +++ b/chalk/core/kis_paint_device.h @@ -0,0 +1,597 @@ +/* + * 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 <tqcolor.h> +#include <tqobject.h> +#include <tqpixmap.h> +#include <tqptrlist.h> +#include <tqrect.h> +#include <tqvaluelist.h> +#include <tqstring.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 TQImage; +class TQSize; +class TQPoint; +class TQWMatrix; +class TQTimer; + +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 TQObject + , public KShared +{ + + Q_OBJECT + TQ_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 + * tqparentLayer will be notified of changes to this paint device. + * + * @param tqparentLayer the layer that contains this paint device. + * @param colorSpace the colorspace of this paint device + * @param name for debugging purposes + */ + KisPaintDevice(KisLayer *tqparentLayer, 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(TQ_INT32 x, TQ_INT32 y); + + /** + * Convenience method for the above + */ + virtual void move(const TQPoint& pt); + + /** + * Move the paint device to the specified location and make it possible to + * undo the move. + */ + virtual KNamedCommand * moveCommand(TQ_INT32 x, TQ_INT32 y); + + /** + * Returns true of x,y is within the extent of this paint device + */ + bool tqcontains(TQ_INT32 x, TQ_INT32 y) const; + + /** + * Convenience method for the above + */ + bool tqcontains(const TQPoint& 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(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const; + virtual TQRect 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(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const; + virtual TQRect exactBounds() const; + virtual TQRect exactBoundsOldMethod() const; + virtual TQRect exactBoundsImprovedOldMethod() const; + + /** + * Cut the paint device down to the specified rect + */ + void crop(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + /// Convience method for the above + void crop(TQRect 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(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const TQ_UINT8 *fillPixel); + + /** + * Read the bytes representing the rectangle described by x, y, w, h into + * data. If data is not big enough, Chalk 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(TQ_UINT8 * data, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + /** + * Copy the bytes in data into the rect specified by x, y, w, h. If the + * data is too small or uninitialized, Chalk 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 TQ_UINT8 * data, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + /** + * Get the number of contiguous columns starting at x, valid for all values + * of y between minY and maxY. + */ + TQ_INT32 numContiguousColumns(TQ_INT32 x, TQ_INT32 minY, TQ_INT32 maxY); + + /** + * Get the number of contiguous rows starting at y, valid for all values + * of x between minX and maxX. + */ + TQ_INT32 numContiguousRows(TQ_INT32 y, TQ_INT32 minX, TQ_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). + */ + TQ_INT32 rowStride(TQ_INT32 x, TQ_INT32 y); + + /** + * Get a read-only pointer to pixel (x, y). + */ + KDE_DEPRECATED const TQ_UINT8* pixel(TQ_INT32 x, TQ_INT32 y); + + /** + * Get a read-write pointer to pixel (x, y). + */ + KDE_DEPRECATED TQ_UINT8* writablePixel(TQ_INT32 x, TQ_INT32 y); + + /** + * Converts the paint device to a different colorspace + */ + virtual void convertTo(KisColorSpace * dstColorSpace, TQ_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 convertFromTQImage(const TQImage& img, const TQString &srcProfileName, TQ_INT32 offsetX = 0, TQ_INT32 offsetY = 0); + + /** + * Create an RGBA TQImage 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 TQImage convertToTQImage(KisProfile * dstProfile, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, float exposure = 0.0f); + + /** + * Create an RGBA TQImage from a rectangle in the paint device. The rectangle is defined by the tqparent 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 TQImage convertToTQImage(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(TQ_INT32 w, TQ_INT32 h); + + /** + * Creates a thumbnail of the paint device, retaining the aspect ratio. + * The width and height of the returned TQImage won't exceed \p maxw and \p maxw, but they may be smaller. + * The colors are not corrected for display! + */ + virtual TQImage createThumbnail(TQ_INT32 maxw, TQ_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(TQ_INT32 x, TQ_INT32 y, TQColor *c, TQ_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(TQ_INT32 x, TQ_INT32 y, KisColor * kc); + + /** + * Return the KisColor of the pixel at x,y. + */ + KisColor colorAt(TQ_INT32 x, TQ_INT32 y); + + /** + * Set the specified pixel to the specified color. Note that this + * bypasses KisPainter. the PaintDevice is here used as an equivalent + * to TQImage, not TQPixmap. 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(TQ_INT32 x, TQ_INT32 y, const TQColor& c, TQ_UINT8 opacity); + + bool setPixel(TQ_INT32 x, TQ_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 + */ + TQ_INT32 getX() const; + + /** + * The Y offset of the paint device + */ + TQ_INT32 getY() const; + + /** + * Return the X offset of the paint device + */ + void setX(TQ_INT32 x); + + /** + * Return the Y offset of the paint device + */ + void setY(TQ_INT32 y); + + + /** + * Return the number of bytes a pixel takes. + */ + virtual TQ_INT32 pixelSize() const; + + /** + * Return the number of channels a pixel takes + */ + virtual TQ_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 tqparentLayer()->image(). + */ + KisImage *image() const; + + /** + * Returns the KisLayer that contains this paint device, or 0 if this is not + * part of a layer. + */ + KisLayer *tqparentLayer() const; + + /** + * Set the KisLayer that contains this paint device, or 0 if this is not + * part of a layer. + */ + void setParentLayer(KisLayer *tqparentLayer); + + /** + * Add the specified rect top the tqparent layer (if present) + */ + virtual void setDirty(const TQRect & rc); + + /** + * Set the tqparent 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(TQ_INT32 left, TQ_INT32 top, TQ_INT32 w, TQ_INT32 h, bool writable); + + /** + * This function return an iterator which points to the first pixel of a horizontal line + */ + KisHLineIteratorPixel createHLineIterator(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, bool writable); + + /** + * This function return an iterator which points to the first pixel of a vertical line + */ + KisVLineIteratorPixel createVLineIterator(TQ_INT32 x, TQ_INT32 y, TQ_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(TQ_INT32 x, TQ_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 tqmask to the image data, i.e. multiply each pixel's opacity by its + * selectedness in the tqmask. + */ + void applySelectionMask(KisSelectionSP tqmask); + + /** + * 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 TQRect& 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(TQ_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; + + TQ_INT32 m_x; + TQ_INT32 m_y; + KisColorSpace * m_colorSpace; + // Cached for quick access + TQ_INT32 m_pixelSize; + TQ_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; + + TQValueList<KisFilter*> m_longRunningFilters; + TQTimer * m_longRunningFilterTimer; + + bool m_lock; +}; + +inline TQ_INT32 KisPaintDevice::pixelSize() const +{ + Q_ASSERT(m_pixelSize > 0); + return m_pixelSize; +} + +inline TQ_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 TQ_INT32 KisPaintDevice::getX() const +{ + return m_x; +} + +inline TQ_INT32 KisPaintDevice::getY() const +{ + return m_y; +} + +#endif // KIS_PAINT_DEVICE_IMPL_H_ + diff --git a/chalk/core/kis_paint_device_action.h b/chalk/core/kis_paint_device_action.h new file mode 100644 index 00000000..1e3d41f0 --- /dev/null +++ b/chalk/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 TQString; + +/** + * 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, TQ_INT32 w = 0, TQ_INT32 h = 0) const = 0; + /// The name of the action, to be displayed in the GUI + virtual TQString name() const = 0; + /// A description of the action, to be displayed in the GUI + virtual TQString description() const = 0; +}; + +#endif // KIS_PAINTDEV_ACTION_H_ diff --git a/chalk/core/kis_paint_device_iface.cc b/chalk/core/kis_paint_device_iface.cc new file mode 100644 index 00000000..1aace538 --- /dev/null +++ b/chalk/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 * tqparent ) + : DCOPObject("paintdevice") +{ + m_parent = tqparent; +} + +TQ_INT32 KisPaintDeviceIface::pixelSize() const +{ + return m_parent->pixelSize(); +} + +TQ_INT32 KisPaintDeviceIface::nChannels() const +{ + return m_parent->nChannels(); +} + +TQByteArray KisPaintDeviceIface::readBytes(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + TQByteArray b (w * h * m_parent->pixelSize()); + + m_parent->readBytes((TQ_UINT8*)b.data(), x, y, w, h); + return b; +} + +void KisPaintDeviceIface::writeBytes(TQByteArray bytes, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + m_parent->writeBytes((TQ_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/chalk/core/kis_paint_device_iface.h b/chalk/core/kis_paint_device_iface.h new file mode 100644 index 00000000..df7c9372 --- /dev/null +++ b/chalk/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 <tqstring.h> + +class KisPaintDevice; + +class KisPaintDeviceIface : virtual public DCOPObject +{ + K_DCOP +public: + KisPaintDeviceIface( KisPaintDevice * tqparent ); +k_dcop: + + /** + * Return the number of bytes a pixel takes. + */ + TQ_INT32 pixelSize() const; + + /** + * Return the number of channels a pixel takes + */ + TQ_INT32 nChannels() const; + + /** + * Read the bytes representing the rectangle described by x, y, w, h into + * data. If data is not big enough, Chalk 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. + */ + TQByteArray readBytes(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + /** + * Copy the bytes in data into the rect specified by x, y, w, h. If there + * data is too small or uninitialized, Chalk 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(TQByteArray bytes, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_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/chalk/core/kis_paint_layer.cc b/chalk/core/kis_paint_layer.cc new file mode 100644 index 00000000..63663067 --- /dev/null +++ b/chalk/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 <tqimage.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 TQString& name, TQ_UINT8 opacity, KisPaintDeviceSP dev) + : super(img, name, opacity) +{ + Q_ASSERT(img); + Q_ASSERT(dev); + m_paintdev = dev; + m_tqmask = 0; + m_tqmaskAsSelection = 0; + m_paintdev->setParentLayer(this); + m_renderMask = false; + m_editMask = true; +} + + +KisPaintLayer::KisPaintLayer(KisImage *img, const TQString& name, TQ_UINT8 opacity) + : super(img, name, opacity) +{ + Q_ASSERT(img); + m_paintdev = new KisPaintDevice(this, img->colorSpace(), name.latin1()); + m_tqmask = 0; + m_tqmaskAsSelection = 0; + m_renderMask = false; + m_editMask = true; +} + +KisPaintLayer::KisPaintLayer(KisImage *img, const TQString& name, TQ_UINT8 opacity, KisColorSpace * colorSpace) + : super(img, name, opacity) +{ + Q_ASSERT(img); + Q_ASSERT(colorSpace); + m_paintdev = new KisPaintDevice(this, colorSpace, name.latin1()); + m_tqmask = 0; + m_tqmaskAsSelection = 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_tqmask = new KisPaintDevice(*rhs.m_tqmask.data()); + m_tqmask->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_tqmask != 0) { + m_tqmask->setParentLayer(0); + } +} + +void KisPaintLayer::paintSelection(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + if (m_paintdev && m_paintdev->hasSelection()) { + m_paintdev->selection()->paintSelection(img, x, y, w, h); + } else if (m_tqmask && m_editMask && m_tqmask->hasSelection()) { + m_tqmask->selection()->paintSelection(img, x, y, w, h); + } +} + +void KisPaintLayer::paintSelection(TQImage &img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize) +{ + if (m_paintdev && m_paintdev->hasSelection()) { + m_paintdev->selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); + } else if (m_tqmask && m_editMask && m_tqmask->hasSelection()) { + m_tqmask->selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); + } +} + +void KisPaintLayer::paintMaskInactiveLayers(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + uchar *j = img.bits(); + + KisColorSpace *cs = m_paintdev->colorSpace(); + + for (TQ_INT32 y2 = y; y2 < h + y; ++y2) { + KisHLineIteratorPixel it = m_paintdev->createHLineIterator(x, y2, w, false); + while ( ! it.isDone()) { + TQ_UINT8 s = cs->getAlpha(it.rawData()); + if(s==0) + { + TQ_UINT8 g = (*(j + 0) + *(j + 1 ) + *(j + 2 )) / 9; + + *(j+0) = 128+g ; + *(j+1) = 165+g; + *(j+2) = 128+g; + } + j+=4; + ++it; + } + } +} + +TQImage KisPaintLayer::createThumbnail(TQ_INT32 w, TQ_INT32 h) +{ + if (m_paintdev) + return m_paintdev->createThumbnail(w, h); + else + return TQImage(); +} + + +TQ_INT32 KisPaintLayer::x() const { + if (m_paintdev) + return m_paintdev->getX(); + else return 0; +} + +void KisPaintLayer::setX(TQ_INT32 x) +{ + if (m_paintdev) + m_paintdev->setX(x); +} + +TQ_INT32 KisPaintLayer::y() const { + if (m_paintdev) + return m_paintdev->getY(); + else + return 0; +} + +void KisPaintLayer::setY(TQ_INT32 y) { + if (m_paintdev) + m_paintdev->setY(y); +} + +TQRect KisPaintLayer::extent() const { + if (m_paintdev) + return m_paintdev->extent(); + else + return TQRect(); +} + +TQRect KisPaintLayer::exactBounds() const { + if (m_paintdev) + return m_paintdev->exactBounds(); + else + return TQRect(); +} + +void KisPaintLayer::removeMask() { + if (!hasMask()) + return; + + m_tqmask->setParentLayer(0); + m_tqmask = 0; + m_tqmaskAsSelection = 0; + setDirty(); + + emit sigMaskInfoChanged(); +} + +// ### XXX Do we apply the tqmask 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_tqmaskAsSelection, 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_tqmask; + + kdDebug() << k_funcinfo << endl; + // Grey8 nicely fits our needs of being intuitively comparable to other apps' + // tqmask layer interfaces. It does have an alpha component though, which is a bit + // less appropriate in this context. + m_tqmask = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry() + ->getColorSpace(KisID("GRAYA"), 0)); + + genericMaskCreationHelper(); + + return m_tqmask; +} + +// 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_tqmask = from; // KisPaintDevice(*from); XXX + + genericMaskCreationHelper(); +} + +void KisPaintLayer::createMaskFromSelection(KisSelectionSP from) { + kdDebug() << k_funcinfo << endl; + m_tqmask = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry() + ->getColorSpace(KisID("GRAYA"), 0)); + m_tqmask->setParentLayer(this); + + m_tqmaskAsSelection = new KisSelection(); // Anonymous selection is good enough + + // Default pixel is opaque white == don't tqmask? + TQ_UINT8 const defPixel[] = { 255, 255 }; + m_tqmask->dataManager()->setDefaultPixel(defPixel); + + if (from) { + TQRect 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_tqmask->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_tqmask; +} + +KisSelectionSP KisPaintLayer::getMaskAsSelection() { + createMask(); + kdDebug() << k_funcinfo << endl; + return m_tqmaskAsSelection; +} + +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 TQRect& r) { + KisRectIteratorPixel srcIt = m_tqmask->createRectIterator(r.x(), r.y(), + r.width(), r.height(), false); + KisRectIteratorPixel dstIt = m_tqmaskAsSelection->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_tqmask->setParentLayer(this); + + m_tqmaskAsSelection = new KisSelection(); // Anonymous selection is good enough + + // Default pixel is opaque white == don't tqmask? + TQ_UINT8 const defPixel[] = { 255, 255 }; + m_tqmask->dataManager()->setDefaultPixel(defPixel); + + setDirty(); + emit sigMaskInfoChanged(); +} + +void KisPaintLayer::setDirty(bool propagate) { + if (hasMask()) + convertMaskToSelection(extent()); + super::setDirty(propagate); +} + +void KisPaintLayer::setDirty(const TQRect & 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_tqmask; + public: + KisCreateMaskCommand(const TQString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) {} + virtual void execute() { + kdDebug() << k_funcinfo << endl; + if (!m_tqmask) + m_tqmask = m_layer->createMask(); + else + m_layer->createMaskFromPaintDevice(m_tqmask); + } + virtual void unexecute() { + m_layer->removeMask(); + } + }; + + class KisMaskFromSelectionCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_tqmaskBefore; + KisPaintDeviceSP m_tqmaskAfter; + KisSelectionSP m_selection; + public: + KisMaskFromSelectionCommand(const TQString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + if (m_layer->hasMask()) + m_tqmaskBefore = m_layer->getMask(); + else + m_tqmaskBefore = 0; + m_tqmaskAfter = 0; + if (m_layer->paintDevice()->hasSelection()) + m_selection = m_layer->paintDevice()->selection(); + else + m_selection = 0; + } + virtual void execute() { + if (!m_tqmaskAfter) { + m_layer->createMaskFromSelection(m_selection); + m_tqmaskAfter = m_layer->getMask(); + } else { + m_layer->paintDevice()->deselect(); + m_layer->createMaskFromPaintDevice(m_tqmaskAfter); + } + } + virtual void unexecute() { + m_layer->paintDevice()->setSelection(m_selection); + if (m_tqmaskBefore) + m_layer->createMaskFromPaintDevice(m_tqmaskBefore); + else + m_layer->removeMask(); + } + }; + + class KisMaskToSelectionCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_tqmask; + KisSelectionSP m_selection; + public: + KisMaskToSelectionCommand(const TQString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_tqmask = 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_tqmask); + } + }; + + class KisRemoveMaskCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_tqmask; + public: + KisRemoveMaskCommand(const TQString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_tqmask = 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_tqmask); + } + }; + + class KisApplyMaskCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_tqmask; + KisPaintDeviceSP m_original; + public: + KisApplyMaskCommand(const TQString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_tqmask = 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_tqmask); + } + }; +} + +KNamedCommand* KisPaintLayer::createMaskCommand() { + return new KisCreateMaskCommand(i18n("Create Layer Mask"), this); +} + +KNamedCommand* KisPaintLayer::tqmaskFromSelectionCommand() { + return new KisMaskFromSelectionCommand(i18n("Mask From Selection"), this); +} + +KNamedCommand* KisPaintLayer::tqmaskToSelectionCommand() { + 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/chalk/core/kis_paint_layer.h b/chalk/core/kis_paint_layer.h new file mode 100644 index 00000000..9e598e7a --- /dev/null +++ b/chalk/core/kis_paint_layer.h @@ -0,0 +1,157 @@ +/* + * 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 + TQ_OBJECT + +public: + KisPaintLayer(KisImage *img, const TQString& name, TQ_UINT8 opacity, KisPaintDeviceSP dev); + KisPaintLayer(KisImage *img, const TQString& name, TQ_UINT8 opacity); + KisPaintLayer(KisImage *img, const TQString& name, TQ_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 TQ_INT32 x() const; + virtual void setX(TQ_INT32 x); + + virtual TQ_INT32 y() const; + virtual void setY(TQ_INT32 y); + + virtual TQRect extent() const; + virtual TQRect exactBounds() const; + + virtual void paintSelection(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + virtual void paintSelection(TQImage &img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize); + + virtual void paintMaskInactiveLayers(TQImage &img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + + virtual TQImage createThumbnail(TQ_INT32 w, TQ_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 tqmask, see editMask) + inline KisPaintDeviceSP paintDeviceOrMask() const { + if (hasMask() && editMask()) + return m_tqmask; + return m_paintdev; + } + + // Mask Layer + + /// Does this layer have a layer tqmask? + bool hasMask() const { return m_tqmask != 0; } + // XXX TODO: Make these undo-able! + /// Create a tqmask if it does not yet exist, and return it + KisPaintDeviceSP createMask(); + /// Convert the from argument to the tqmask + void createMaskFromPaintDevice(KisPaintDeviceSP from); + /** + * Convert the from selection to a paint device (should convert the getMaskAsSelection + * result back to the tqmask). Overwrites the current tqmask, if any. Also removes the selection + */ + void createMaskFromSelection(KisSelectionSP from); + /// Remove the layer tqmask + void removeMask(); + /// Apply the layer tqmask to the paint device, this removes the tqmask afterwards + void applyMask(); + /// Returns the layer tqmask's device. Creates one if there is currently none + KisPaintDeviceSP getMask(); + /// Returns the layer tqmask'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* tqmaskFromSelectionCommand(); + /// Undoable, removes the current tqmask, but converts it to the current selection + KNamedCommand* tqmaskToSelectionCommand(); + /// Undoable version of removeMask + KNamedCommand* removeMaskCommand(); + /// Undoable version of applyMask + KNamedCommand* applyMaskCommand(); + + /// Returns true if the tqmasked part of the tqmask 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 tqmask (if there is one). This is so that tools can draw on the tqmask + * 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 TQRect & rect, bool propagate = true); + + // KisLayerSupportsIndirectPainting + virtual KisLayer* layer() { return this; } +signals: + /// When the tqmask is created/destroyed or the edittqmask or rendertqmask is changed + void sigMaskInfoChanged(); + +private: + void convertMaskToSelection(const TQRect& r); + void genericMaskCreationHelper(); + KisPaintDeviceSP m_paintdev; + // Layer tqmask related: + // XXX It would be nice to merge the next 2 devices... + KisPaintDeviceSP m_tqmask; // The tqmask that we can edit and display easily + KisSelectionSP m_tqmaskAsSelection; // The tqmask 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/chalk/core/kis_painter.cc b/chalk/core/kis_painter.cc new file mode 100644 index 00000000..b8353918 --- /dev/null +++ b/chalk/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 "tqbrush.h" +#include "tqfontinfo.h" +#include "tqfontmetrics.h" +#include "tqpen.h" +#include "tqregion.h" +#include "tqwmatrix.h" +#include <tqimage.h> +#include <tqmap.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqpointarray.h> +#include <tqrect.h> +#include <tqstring.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 TQString& 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; +} + + +TQRect KisPainter::dirtyRect() { + TQRect r = m_dirtyRect; + m_dirtyRect = TQRect(); + return r; +} + +void KisPainter::bitBlt(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP srcdev, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh) +{ + if (srcdev == 0) { + return; + } + + TQRect srcRect = TQRect(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(TQRect(dx, dy, sw, sh)); + + KisColorSpace * srcCs = srcdev->colorSpace(); + + TQ_INT32 dstY = dy; + TQ_INT32 srcY = sy; + TQ_INT32 rowsRemaining = sh; + + while (rowsRemaining > 0) { + + TQ_INT32 dstX = dx; + TQ_INT32 srcX = sx; + TQ_INT32 columnsRemaining = sw; + TQ_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1); + TQ_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1); + + TQ_INT32 rows = TQMIN(numContiguousDstRows, numContiguousSrcRows); + rows = TQMIN(rows, rowsRemaining); + + while (columnsRemaining > 0) { + + TQ_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1); + TQ_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1); + + TQ_INT32 columns = TQMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = TQMIN(columns, columnsRemaining); + + TQ_INT32 srcRowStride = srcdev->rowStride(srcX, srcY); + //const TQ_UINT8 *srcData = srcdev->pixel(srcX, srcY); + KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false); + const TQ_UINT8 *srcData = srcIt.rawData(); + + //TQ_UINT8 *dstData = m_device->writablePixel(dstX, dstY); + TQ_INT32 dstRowStride = m_device->rowStride(dstX, dstY); + KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true); + TQ_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(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP srcdev, + KisSelectionSP seldev, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh) +{ + // Better use a probablistic method than a too slow one + if (seldev->isProbablyTotallyUnselected(TQRect(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(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP srcdev, + KisPaintDeviceSP seldev, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh) + +{ + if (srcdev == 0) return; + + if (seldev == 0) return; + + if (m_device == 0) return; + + + TQRect srcRect = TQRect(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(TQRect(dx, dy, sw, sh)); + + KisColorSpace * srcCs = srcdev->colorSpace(); + + TQ_INT32 dstY = dy; + TQ_INT32 srcY = sy; + TQ_INT32 rowsRemaining = sh; + + while (rowsRemaining > 0) { + + TQ_INT32 dstX = dx; + TQ_INT32 srcX = sx; + TQ_INT32 columnsRemaining = sw; + TQ_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1); + TQ_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1); + TQ_INT32 numContiguousSelRows = seldev->numContiguousRows(dstY, dstX, dstX + sw - 1); + + TQ_INT32 rows = TQMIN(numContiguousDstRows, numContiguousSrcRows); + rows = TQMIN(numContiguousSelRows, rows); + rows = TQMIN(rows, rowsRemaining); + + while (columnsRemaining > 0) { + + TQ_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1); + TQ_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1); + TQ_INT32 numContiguousSelColumns = seldev->numContiguousColumns(dstX, dstY, dstY + rows - 1); + + TQ_INT32 columns = TQMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = TQMIN(numContiguousSelColumns, columns); + columns = TQMIN(columns, columnsRemaining); + + //TQ_UINT8 *dstData = m_device->writablePixel(dstX, dstY); + TQ_INT32 dstRowStride = m_device->rowStride(dstX, dstY); + KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true); + TQ_UINT8 *dstData = dstIt.rawData(); + + //const TQ_UINT8 *srcData = srcdev->pixel(srcX, srcY); + TQ_INT32 srcRowStride = srcdev->rowStride(srcX, srcY); + KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false); + const TQ_UINT8 *srcData = srcIt.rawData(); + + //const TQ_UINT8 *selData = seldev->pixel(dstX, dstY); + TQ_INT32 selRowStride = seldev->rowStride(dstX, dstY); + KisHLineIteratorPixel selIt = seldev->createHLineIterator(dstX, dstY, columns, false); + const TQ_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(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP srcdev, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_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 tqmask for the actual polygon coverage. + + KisPaintDeviceSP polygon = new KisPaintDevice(m_device->colorSpace(), "polygon"); + Q_CHECK_PTR(polygon); + + KisFillPainter fillPainter(polygon); + TQRect 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); + + TQRect 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/chalk/core/kis_painter.h b/chalk/core/kis_painter.h new file mode 100644 index 00000000..abfd8b17 --- /dev/null +++ b/chalk/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 TQRect; +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 TQt + * itself, where you have TQPainter and TQPaintDevice. + * + * 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 TQString& customName = TQString()); + + /// 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(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP src, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_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(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP src, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh); + + /** + * A version of bitBlt that renders using an external tqmask, ignoring + * the src device's own selection, if it has one. + */ + void bltMask(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + KisPaintDeviceSP selMask, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh); + + /** + * A version of bitBlt that renders using an external selection tqmask, ignoring + * the src device's own selection, if it has one. + */ + void bltSelection(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + KisSelectionSP selMask, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_INT32 sh); + + + /** + * A version of bitBlt that renders using the src device's selection tqmask, if it has one. + */ + void bltSelection(TQ_INT32 dx, TQ_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + TQ_UINT8 opacity, + TQ_INT32 sx, TQ_INT32 sy, + TQ_INT32 sw, TQ_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! + */ + TQRect dirtyRect(); + + /** + * Add the r to the current dirty rect, and return the dirtyRect after adding r to it. + */ + TQRect addDirtyRect(TQRect r) { m_dirtyRect |= r; return m_dirtyRect; } + + + + /** + * Paint a line that connects the dots in points + */ + void paintPolyline(const TQValueVector <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(TQ_UINT8 opacity) { m_opacity = opacity; } + /// Returns the opacity that is used in painting + TQ_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; + + TQRect 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; + TQ_UINT8 m_opacity; + KisCompositeOp m_compositeOp; + KisFilterSP m_filter; + KisPaintOp * m_paintOp; + double m_pressure; + bool m_cancelRequested; + TQ_INT32 m_pixelSize; + KisColorSpace * m_colorSpace; + KisProfile * m_profile; + KisPaintDeviceSP m_dab; + bool m_varyBrushSpacingWithPressureWhenDrawingALine; + +}; + + +#endif // KIS_PAINTER_H_ + diff --git a/chalk/core/kis_paintop.cc b/chalk/core/kis_paintop.cc new file mode 100644 index 00000000..71989bd9 --- /dev/null +++ b/chalk/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 "tqwidget.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 tqmask) { + return computeDab(tqmask, m_painter->device()->colorSpace()); +} + +KisPaintDeviceSP KisPaintOp::computeDab(KisAlphaMaskSP tqmask, 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(); + + TQ_INT32 pixelSize = colorSpace->pixelSize(); + + TQ_INT32 tqmaskWidth = tqmask->width(); + TQ_INT32 tqmaskHeight = tqmask->height(); + + // Convert the kiscolor to the right colorspace. + kc.convertTo(colorSpace); + + KisHLineIteratorPixel hiter = m_dab->createHLineIterator(0, 0, tqmaskWidth, true); + for (int y = 0; y < tqmaskHeight; y++) + { + int x=0; + while(! hiter.isDone()) + { + // XXX: Set tqmask + colorSpace->setAlpha(kc.data(), tqmask->alphaAt(x++, y), 1); + memcpy(hiter.rawData(), kc.data(), pixelSize); + ++hiter; + } + hiter.nextRow(); + } + + return m_dab; +} + +void KisPaintOp::splitCoordinate(double coordinate, TQ_INT32 *whole, double *fraction) +{ + TQ_INT32 i = static_cast<TQ_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(TQWidget* /*tqparent*/, const KisInputDevice& /*inputDevice*/) { return 0; } diff --git a/chalk/core/kis_paintop.h b/chalk/core/kis_paintop.h new file mode 100644 index 00000000..8e8587ea --- /dev/null +++ b/chalk/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 <tqstring.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 TQWidget; + +/** + * 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 tqmask); + virtual KisPaintDeviceSP computeDab(KisAlphaMaskSP tqmask, KisColorSpace *cs); + + + /** + * Split the coordinate into whole + fraction, where fraction is always >= 0. + */ + virtual void splitCoordinate(double coordinate, TQ_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(TQWidget *tqparent) { Q_UNUSED(tqparent); } + virtual ~KisPaintOpSettings() {} + + virtual TQWidget *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 TQString 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(TQWidget* tqparent, const KisInputDevice& inputDevice); + +}; +#endif // KIS_PAINTOP_H_ diff --git a/chalk/core/kis_paintop_registry.cc b/chalk/core/kis_paintop_registry.cc new file mode 100644 index 00000000..3651f35d --- /dev/null +++ b/chalk/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 <tqpixmap.h> +#include <tqwidget.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(TQString::tqfromLatin1("Chalk/Paintop"), + TQString::tqfromLatin1("(Type == 'Service') and " + "([X-Chalk-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, TQStringList(), &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 TQString & id, const KisPaintOpSettings * settings, KisPainter * painter) const +{ + return paintOp(KisID(id, ""), settings, painter); +} + +KisPaintOpSettings * KisPaintOpRegistry::settings(const KisID& id, TQWidget * tqparent, const KisInputDevice& inputDevice) const +{ + KisPaintOpFactory* f = get(id); + if (f) + return f->settings( tqparent, 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); + +} + +TQString 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/chalk/core/kis_paintop_registry.h b/chalk/core/kis_paintop_registry.h new file mode 100644 index 00000000..c61105b6 --- /dev/null +++ b/chalk/core/kis_paintop_registry.h @@ -0,0 +1,84 @@ +/* + * 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 <tqobject.h> + +#include "kis_types.h" +#include "kis_generic_registry.h" +#include <koffice_export.h> + +class TQWidget; +class TQStringList; + +class KisPaintOp; +class KisPaintOpSettings; +class KisPainter; +class KisColorSpace; +class KisInputDevice; + +class KRITACORE_EXPORT KisPaintOpRegistry : public TQObject, public KisGenericRegistry<KisPaintOpFactorySP> +{ + + Q_OBJECT + TQ_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 TQString& 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 tqparent as widget tqparent. Returns 0 if there + * are no settings available for the given device. + */ + KisPaintOpSettings * settings(const KisID& id, TQWidget * tqparent, 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 + TQString 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/chalk/core/kis_palette.cc b/chalk/core/kis_palette.cc new file mode 100644 index 00000000..daf8d84d --- /dev/null +++ b/chalk/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 <tqimage.h> +#include <tqpoint.h> +#include <tqvaluevector.h> +#include <tqfile.h> +#include <tqtextstream.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 TQImage * img, TQ_INT32 nColors, const TQString & name) + : super(TQString("")), + 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, TQ_INT32 nColors, const TQString & name) + : super(TQString("")), + 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, TQ_INT32 nColors, const TQString & name) + : super(TQString("")), + m_name(name) +{ + Q_ASSERT(nColors > 0); + Q_ASSERT(gradient != 0); + + double dx, cur_x; + TQColor c; + TQ_INT32 i; + TQ_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 TQString& 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() +{ + TQFile file(filename()); + file.open(IO_ReadOnly); + m_data = file.readAll(); + file.close(); + return init(); +} + + +bool KisPalette::save() +{ + TQFile file(filename()); + if (!file.open(IO_WriteOnly | IO_Truncate)) { + return false; + } + + TQTextStream 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); + TQColor 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; +} + +TQImage KisPalette::img() +{ + return m_img; +} + +TQ_INT32 KisPalette::nColors() +{ + return m_colors.count(); +} + +bool KisPalette::init() +{ + enumPaletteType format = FORMAT_UNKNOWN; + + TQString s = TQString::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! + TQ_UINT32 index = 0; + + TQStringList lines = TQStringList::split("\n", s); + + if (lines.size() < 3) { + return false; + } + + TQString entry, channel, columns; + TQStringList c; + TQ_INT32 r, g, b; + TQColor 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 (TQ_UINT32 i = index; i < lines.size(); i++) { + if (lines[i].startsWith("#")) { + m_comment += lines[i].mid(1).stripWhiteSpace() + " "; + } + else if (!lines[i].isEmpty()) + { + TQStringList a = TQStringList::split(" ", lines[i].tqreplace(TQChar('\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 = TQColor(r, g, b); + e.color = color; + + TQString 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) +{ + TQValueVector<KisPaletteEntry>::iterator it = m_colors.begin(); + TQValueVector<KisPaletteEntry>::iterator end = m_colors.end(); + + while (it != end) { + if ((*it) == c) { + m_colors.erase(it); + return; + } + ++it; + } +} + +KisPaletteEntry KisPalette::getColor(TQ_UINT32 index) +{ + return m_colors[index]; +} + +#include "kis_palette.moc" diff --git a/chalk/core/kis_palette.h b/chalk/core/kis_palette.h new file mode 100644 index 00000000..ed49849f --- /dev/null +++ b/chalk/core/kis_palette.h @@ -0,0 +1,113 @@ +/* + * 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 <tqimage.h> +#include <tqcolor.h> +#include <tqvaluevector.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 TQPoint; +class TQPixmap; +class KisPaintDevice; + +struct KisPaletteEntry { + TQColor color; + TQString 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 + TQ_OBJECT + +public: + /** + * Create a palette from the colours in an image + */ + KisPalette(const TQImage * img, TQ_INT32 nColors, const TQString & name); + + /** + * Create a palette from the colours in a paint device + */ + KisPalette(const KisPaintDeviceSP device, TQ_INT32 nColors, const TQString & name); + + /** + * Create a palette from the colours in a gradient + */ + KisPalette(const KisGradient * gradient, TQ_INT32 nColors, const TQString & name); + + /** + * Load a palette from a file. This can be a Gimp + * palette, a RIFF palette or a Photoshop palette. + */ + KisPalette(const TQString& 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 TQImage img(); + + +public: + + void add(const KisPaletteEntry &); + void remove(const KisPaletteEntry &); + KisPaletteEntry getColor(TQ_UINT32 index); + TQ_INT32 nColors(); + +private: + bool init(); + +private: + + TQByteArray m_data; + bool m_ownData; + TQImage m_img; + TQString m_name; + TQString m_comment; + TQ_INT32 m_columns; + TQValueVector<KisPaletteEntry> m_colors; + +}; +#endif // KIS_PALETTE_ + diff --git a/chalk/core/kis_part_layer_iface.h b/chalk/core/kis_part_layer_iface.h new file mode 100644 index 00000000..1d4f7b99 --- /dev/null +++ b/chalk/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 <tqdom.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 TQString &name, TQ_UINT8 opacity) + : super(img, name, opacity) {} + virtual KisPaintDeviceSP prepareProjection(KisPaintDeviceSP projection, const TQRect& r) = 0; + virtual bool saveToXML(TQDomDocument doc, TQDomElement elem) = 0; +}; + +#endif // KIS_PART_IFACE_LAYER_IFACE_ diff --git a/chalk/core/kis_pattern.cc b/chalk/core/kis_pattern.cc new file mode 100644 index 00000000..516d3d8b --- /dev/null +++ b/chalk/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 <tqpoint.h> +#include <tqsize.h> +#include <tqimage.h> +#include <tqvaluevector.h> +#include <tqmap.h> +#include <tqfile.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_color.h" +#include "kis_layer.h" +#include "kis_paint_device.h" + +namespace { + struct GimpPatternHeader { + TQ_UINT32 header_size; /* header_size = sizeof (PatternHeader) + brush name */ + TQ_UINT32 version; /* pattern file version # */ + TQ_UINT32 width; /* width of pattern */ + TQ_UINT32 height; /* height of pattern */ + TQ_UINT32 bytes; /* depth of pattern in bytes : 1, 2, 3 or 4*/ + TQ_UINT32 magic_number; /* GIMP brush magic number */ + }; + + // Yes! This is _NOT_ what my pat.txt file says. It's really not 'GIMP', but 'GPAT' + TQ_UINT32 const GimpPatternMagic = (('G' << 24) + ('P' << 16) + ('A' << 8) + ('T' << 0)); +} + +KisPattern::KisPattern(const TQString& 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->convertToTQImage(0, x, y, w, h)); + setName(image->name()); +} + +KisPattern::~KisPattern() +{ +} + +bool KisPattern::load() +{ + if (!m_hasFile) + return true; + + TQFile file(filename()); + file.open(IO_ReadOnly); + TQByteArray data = file.readAll(); + if (!data.isEmpty()) { + TQ_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() +{ + TQFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + + TQTextStream 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; + TQCString utf8Name = name().utf8(); + char const* name = utf8Name.data(); + int nameLength = tqstrlen(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); + + TQByteArray 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 (TQ_INT32 y = 0; y < height(); y++) { + for (TQ_INT32 x = 0; x < width(); x++) { + // RGBA only + TQRgb pixel = m_img.pixel(x,y); + bytes[k++] = static_cast<char>(tqRed(pixel)); + bytes[k++] = static_cast<char>(tqGreen(pixel)); + bytes[k++] = static_cast<char>(tqBlue(pixel)); + bytes[k++] = static_cast<char>(tqAlpha(pixel)); + } + } + + wrote = file.writeBlock(bytes); + if (wrote == -1) + return false; + + file.close(); + + return true; +} + +TQImage KisPattern::img() +{ + return m_img; +} + +bool KisPattern::init() +{ + // load Gimp patterns + GimpPatternHeader bh; + TQ_INT32 k; + TQValueVector<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 + TQ_INT32 val; + + for (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++, k++) { + if (static_cast<TQ_UINT32>(k) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in gray\n"; + return false; + } + + val = m_data[k]; + m_img.setPixel(x, y, tqRgb(val, val, val)); + m_img.setAlphaBuffer(false); + } + } + } else if (bh.bytes == 2) { + // Grayscale + A + TQ_INT32 val; + TQ_INT32 alpha; + for (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++, k++) { + if (static_cast<TQ_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, tqRgba(val, val, val, alpha)); + m_img.setAlphaBuffer(true); + } + } + } else if (bh.bytes == 3) { + // RGB without alpha + for (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++) { + if (static_cast<TQ_UINT32>(k + 3) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in RGB\n"; + return false; + } + + m_img.setPixel(x, y, tqRgb(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 (TQ_UINT32 y = 0; y < bh.height; y++) { + for (TQ_UINT32 x = 0; x < bh.width; x++) { + if (static_cast<TQ_UINT32>(k + 4) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in RGBA\n"; + return false; + } + + m_img.setPixel(x, y, tqRgba(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 + TQMap<TQString, KisPaintDeviceSP>::const_iterator it = m_colorspaces.tqfind(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->convertFromTQImage(m_img,""); + + m_colorspaces[colorSpace->id().id()] = layer; + return layer; +} + +TQ_INT32 KisPattern::width() const +{ + return m_width; +} + +void KisPattern::setWidth(TQ_INT32 w) +{ + m_width = w; +} + +TQ_INT32 KisPattern::height() const +{ + return m_height; +} + +void KisPattern::setHeight(TQ_INT32 h) +{ + m_height = h; +} + +void KisPattern::setImage(const TQImage& 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/chalk/core/kis_pattern.h b/chalk/core/kis_pattern.h new file mode 100644 index 00000000..7d18f1f3 --- /dev/null +++ b/chalk/core/kis_pattern.h @@ -0,0 +1,80 @@ +/* + * 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 TQPoint; +class TQImage; +class KisColorSpace; +class KisPaintDevice; + +class KisPattern : public KisResource { + typedef KisResource super; + Q_OBJECT + TQ_OBJECT + +public: + KisPattern(const TQString& file); + KisPattern(KisPaintDevice* image, int x, int y, int w, int h); + virtual ~KisPattern(); + + virtual bool load(); + virtual bool save(); + virtual TQImage img(); + + /** + * returns a KisPaintDeviceSP made with colorSpace as the ColorSpace strategy + * for use in the fill painter. + **/ + KisPaintDeviceSP image(KisColorSpace * colorSpace); + + TQ_INT32 width() const; + TQ_INT32 height() const; + + void setImage(const TQImage& img); + + KisPattern* clone() const; + +protected: + void setWidth(TQ_INT32 w); + void setHeight(TQ_INT32 h); + +private: + bool init(); + +private: + TQByteArray m_data; + TQImage m_img; + TQMap<TQString, KisPaintDeviceSP> m_colorspaces; + bool m_hasFile; + + TQ_INT32 m_width; + TQ_INT32 m_height; +}; + +#endif + diff --git a/chalk/core/kis_perspective_grid.cpp b/chalk/core/kis_perspective_grid.cpp new file mode 100644 index 00000000..6eb5fbe8 --- /dev/null +++ b/chalk/core/kis_perspective_grid.cpp @@ -0,0 +1,100 @@ +/* + * This file is part of Chalk + * + * 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::tqcontains(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( TQValueList<KisSubPerspectiveGrid*>::const_iterator it = begin(); it != end(); ++it) + { + delete *it; + } + m_subGrids.clear(); +} + +KisSubPerspectiveGrid* KisPerspectiveGrid::gridAt(KisPoint p) +{ + for( TQValueList<KisSubPerspectiveGrid*>::const_iterator it = begin(); it != end(); ++it) + { + if( (*it)->tqcontains(p) ) + { + return *it; + } + } + return 0; +} + diff --git a/chalk/core/kis_perspective_grid.h b/chalk/core/kis_perspective_grid.h new file mode 100644 index 00000000..4bd22815 --- /dev/null +++ b/chalk/core/kis_perspective_grid.h @@ -0,0 +1,107 @@ +/* + * This file is part of Chalk + * + * 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 <tqvaluelist.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 tqcontains(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 TQValueList<KisSubPerspectiveGrid*>::const_iterator begin() const { return m_subGrids.begin(); } + inline TQValueList<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: + TQValueList<KisSubPerspectiveGrid*> m_subGrids; +}; + +#endif diff --git a/chalk/core/kis_perspective_math.cpp b/chalk/core/kis_perspective_math.cpp new file mode 100644 index 00000000..a4e2124e --- /dev/null +++ b/chalk/core/kis_perspective_math.cpp @@ -0,0 +1,546 @@ +/* + * This file is part of Chalk + * + * 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 <tqrect.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 TQRect& r) +{ + return KisPerspectiveMath::computeMatrixTransfo(topLeft, topRight, bottomLeft, bottomRight, KisPoint(r.topLeft()), KisPoint(r.topRight()), KisPoint(r.bottomLeft()), KisPoint(r.bottomRight())); +} + +double* KisPerspectiveMath::computeMatrixTransfoFromPerspective(const TQRect& r, const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight) +{ + return KisPerspectiveMath::computeMatrixTransfo(KisPoint(r.topLeft()), KisPoint(r.topRight()), KisPoint(r.bottomLeft()), KisPoint(r.bottomRight()), topLeft, topRight, bottomLeft, bottomRight); +} + diff --git a/chalk/core/kis_perspective_math.h b/chalk/core/kis_perspective_math.h new file mode 100644 index 00000000..7cb23770 --- /dev/null +++ b/chalk/core/kis_perspective_math.h @@ -0,0 +1,70 @@ +/* + * This file is part of Chalk + * + * 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 TQRect; + +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 TQRect& r); + static double* computeMatrixTransfoFromPerspective(const TQRect& 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/chalk/core/kis_perspectivetransform_worker.cpp b/chalk/core/kis_perspectivetransform_worker.cpp new file mode 100644 index 00000000..637a61de --- /dev/null +++ b/chalk/core/kis_perspectivetransform_worker.cpp @@ -0,0 +1,121 @@ +/* + * This file is part of Chalk + * + * 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) + +{ + TQRect 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/chalk/core/kis_perspectivetransform_worker.h b/chalk/core/kis_perspectivetransform_worker.h new file mode 100644 index 00000000..f81515cf --- /dev/null +++ b/chalk/core/kis_perspectivetransform_worker.h @@ -0,0 +1,52 @@ +/* + * This file is part of Chalk + * + * 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: + TQ_INT32 m_progressTotalSteps; + TQ_INT32 m_lastProgressReport; + TQ_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]; + TQRect m_r; +}; + +#endif diff --git a/chalk/core/kis_point.h b/chalk/core/kis_point.h new file mode 100644 index 00000000..3c576cb5 --- /dev/null +++ b/chalk/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 <tqvaluevector.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 TQPoint& 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 tqRound(x()); } + int roundY() const { return tqRound(y()); } + + TQPoint floorTQPoint() const { return TQPoint(static_cast<int>(x()), static_cast<int>(y())); } + TQPoint roundTQPoint() const { return TQPoint(tqRound(x()), tqRound(y())); } +}; + +typedef TQValueVector<KisPoint> vKisPoint; + +#endif // KIS_POINT_H_ + diff --git a/chalk/core/kis_random_accessor.cpp b/chalk/core/kis_random_accessor.cpp new file mode 100644 index 00000000..cfa8ecab --- /dev/null +++ b/chalk/core/kis_random_accessor.cpp @@ -0,0 +1,58 @@ +/* + * This file is part of the Chalk 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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 offsetx, TQ_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(TQ_INT32 x, TQ_INT32 y) +{ + m_accessor->moveTo(x - m_offsetx, y - m_offsety); +} + +TQ_UINT8* KisRandomAccessor::rawData() const +{ + return m_accessor->rawData(); +} + +const TQ_UINT8* KisRandomAccessor::oldRawData() const +{ + return m_accessor->oldRawData(); +} + +KisRandomAccessorPixel::KisRandomAccessorPixel(KisTiledDataManager *ktm, KisTiledDataManager *ktmselect, TQ_INT32 x, TQ_INT32 y, TQ_INT32 offsetx, TQ_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/chalk/core/kis_random_accessor.h b/chalk/core/kis_random_accessor.h new file mode 100644 index 00000000..6e2e10ad --- /dev/null +++ b/chalk/core/kis_random_accessor.h @@ -0,0 +1,95 @@ +/* + * This file is part of the Chalk 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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 offsetx, TQ_INT32 offsety, bool writable); + KisRandomAccessor(const KisRandomAccessor& rhs); + ~KisRandomAccessor(); + public: + /// Move to a given x,y position, fetch tiles and data + void moveTo(TQ_INT32 x, TQ_INT32 y); + TQ_UINT8* rawData() const; + const TQ_UINT8* oldRawData() const; + private: + KisTiledRandomAccessorSP m_accessor; + TQ_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 TQ_UINT8 operator[](int index) const + { return m_underlyingAccessor->rawData()[index]; }; + /** + * Returns the degree of selectedness of the pixel. + */ + inline TQ_UINT8 selectedness() const + { + return (m_selectionAccessor) ? *(m_selectionAccessor->rawData()) : MAX_SELECTED; + }; + + /** + * Returns the selectiontqmask 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 TQ_UINT8 * selectionMask() const + { + return ( m_selectionAccessor ) ? m_selectionAccessor->rawData() : 0; + } + + inline void moveTo(TQ_INT32 x, TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 offsetx, TQ_INT32 offsety, bool writable); + public: + inline void moveTo(TQ_INT32 x, TQ_INT32 y) { KisRandomAccessor::moveTo(x,y); KisRandomAccessorPixelTrait::moveTo(x,y); } +}; + + +#endif diff --git a/chalk/core/kis_random_sub_accessor.cpp b/chalk/core/kis_random_sub_accessor.cpp new file mode 100644 index 00000000..a1ce8aa4 --- /dev/null +++ b/chalk/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(TQ_UINT8* dst) +{ + const TQ_UINT8* pixels[4]; + TQ_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)tqRound( ( 1.0 - hsub) * ( 1.0 - vsub) * 255 ); + m_randomAccessor.moveTo(x, y); + pixels[0] = m_randomAccessor.oldRawData(); + weights[1] = (int)tqRound( ( 1.0 - vsub) * hsub * 255 ); + m_randomAccessor.moveTo(x+1, y); + pixels[1] = m_randomAccessor.oldRawData(); + weights[2] = (int)tqRound( vsub * ( 1.0 - hsub) * 255 ); + m_randomAccessor.moveTo(x, y+1); + pixels[2] = m_randomAccessor.oldRawData(); + weights[3] = (int)tqRound( 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(TQ_UINT8* dst) +{ + const TQ_UINT8* pixels[4]; + TQ_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)tqRound( ( 1.0 - hsub) * ( 1.0 - vsub) * 255 ); + m_randomAccessor.moveTo(x, y); + pixels[0] = m_randomAccessor.rawData(); + weights[1] = (int)tqRound( ( 1.0 - vsub) * hsub * 255 ); + m_randomAccessor.moveTo(x+1, y); + pixels[1] = m_randomAccessor.rawData(); + weights[2] = (int)tqRound( vsub * ( 1.0 - hsub) * 255 ); + m_randomAccessor.moveTo(x, y+1); + pixels[2] = m_randomAccessor.rawData(); + weights[3] = (int)tqRound( 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/chalk/core/kis_random_sub_accessor.h b/chalk/core/kis_random_sub_accessor.h new file mode 100644 index 00000000..6ceb8345 --- /dev/null +++ b/chalk/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(TQ_UINT8* dst); + void sampledRawData(TQ_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/chalk/core/kis_rect.cc b/chalk/core/kis_rect.cc new file mode 100644 index 00000000..175a07a9 --- /dev/null +++ b/chalk/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" + +TQRect KisRect::qRect() const +{ + return TQRect(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/chalk/core/kis_rect.h b/chalk/core/kis_rect.h new file mode 100644 index 00000000..30a729c8 --- /dev/null +++ b/chalk/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 <tqrect.h> +#include <KoRect.h> +#include "kis_point.h" + +/** + * A double-based rect class that can return a TQRect 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 TQRect& qr) : super(qr.x(), qr.y(), qr.width(), qr.height()) {} + KisRect(const KoRect& r) : super(r) {} + + /** + * Return the TQRect that encloses this KisRect. + */ + TQRect qRect() const; + +private: + // Use qRect() which uses ceil() and floor() to return a rectangle + // 'enclosing' the rectangle, whereas toTQRect rounds the points. + TQRect toTQRect() const; +}; + +#endif // KIS_RECT_H_ + diff --git a/chalk/core/kis_resource.cc b/chalk/core/kis_resource.cc new file mode 100644 index 00000000..a856cbb1 --- /dev/null +++ b/chalk/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 TQString& filename) +{ + m_filename = filename; + m_valid = false; +} + +KisResource::~KisResource() +{ +} + +TQString KisResource::filename() const +{ + return m_filename; +} + +void KisResource::setFilename(const TQString& filename) +{ + m_filename = filename; +} + +TQString KisResource::name() const +{ + return m_name; +} + +void KisResource::setName(const TQString& 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/chalk/core/kis_resource.h b/chalk/core/kis_resource.h new file mode 100644 index 00000000..e07763e3 --- /dev/null +++ b/chalk/core/kis_resource.h @@ -0,0 +1,84 @@ +/* + * 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 <tqimage.h> +#include <tqobject.h> +#include <tqstring.h> + + +/** + * The KisResource class provides a representation of Chalk 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 TQObject { + typedef TQObject super; + Q_OBJECT + TQ_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 TQString& 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 TQImage representing this resource. This image could be null. + */ + virtual TQImage img() = 0; + +public: + TQString filename() const; + void setFilename(const TQString& filename); + TQString name() const; + void setName(const TQString& name); + bool valid() const; + void setValid(bool valid); + +private: + KisResource(const KisResource&); + KisResource& operator=(const KisResource&); + +private: + TQString m_name; + TQString m_filename; + bool m_valid; +}; + +#endif // KIS_RESOURCE_H_ + diff --git a/chalk/core/kis_rotate_visitor.cc b/chalk/core/kis_rotate_visitor.cc new file mode 100644 index 00000000..2a8bfd45 --- /dev/null +++ b/chalk/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 <tqapplication.h> +#include <tqwmatrix.h> +#include <tqrect.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 { + TQRect 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); + TQRect 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); + + TQRect 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()); + + TQ_INT32 pixelSize = src->pixelSize(); + TQRect r = src->exactBounds(); + TQ_INT32 x = 0; + + for (TQ_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"); + + TQ_INT32 pixelSize = src->pixelSize(); + TQRect r = src->exactBounds(); + TQ_INT32 x = 0; + + for (TQ_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()); + + TQ_INT32 pixelSize = src->pixelSize(); + TQRect r = src->exactBounds(); + + for (TQ_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; + } + + TQRect 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((TQ_INT32)(dst->getX() + centreOfRotation.x() - rotatedCentreOfRotation.x())); + dst->setY((TQ_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()); + + TQRect r = src->exactBounds(); + + double displacement; + TQ_INT32 displacementInt; + double weight; + + for (TQ_INT32 y = r.top(); y <= r.bottom(); y++) { + + //calculate displacement + displacement = -y * shearX; + + displacementInt = (TQ_INT32)(floor(displacement)); + weight = displacement - displacementInt; + + TQ_UINT8 pixelWeights[2]; + + pixelWeights[0] = static_cast<TQ_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 TQ_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()); + + TQRect r = src->exactBounds(); + + double displacement; + TQ_INT32 displacementInt; + double weight; + + for (TQ_INT32 x = r.left(); x <= r.right(); x++) { + + //calculate displacement + displacement = x * shearY; + + displacementInt = (TQ_INT32)(floor(displacement)); + weight = displacement - displacementInt; + + TQ_UINT8 pixelWeights[2]; + + pixelWeights[0] = static_cast<TQ_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 TQ_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(TQ_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++; + TQ_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/chalk/core/kis_rotate_visitor.h b/chalk/core/kis_rotate_visitor.h new file mode 100644 index 00000000..ea23f84f --- /dev/null +++ b/chalk/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 TQRect; +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(TQ_INT32 totalSteps); + void incrementProgress(); + void setProgressDone(); + + KisProgressDisplayInterface *m_progress; + TQ_INT32 m_progressStep; + TQ_INT32 m_progressTotalSteps; + TQ_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/chalk/core/kis_scale_visitor.cc b/chalk/core/kis_scale_visitor.cc new file mode 100644 index 00000000..25ad47c4 --- /dev/null +++ b/chalk/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 <tqdatetime.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(); + + TQRect rect = m_dev -> exactBounds(); + TQ_INT32 width = rect.width(); + TQ_INT32 height = rect.height(); + m_pixelSize=m_dev -> pixelSize(); + + // compute size of target image + if ( m_sx == 1.0F && m_sy == 1.0F ) { + return; + } + TQ_INT32 targetW = TQABS( tqRound( m_sx * width ) ); + TQ_INT32 targetH = TQABS( tqRound( m_sy * height ) ); + + TQ_UINT8* newData = new TQ_UINT8[targetW * targetH * m_pixelSize ]; + Q_CHECK_PTR(newData); + + double* weight = new double[ m_pixelSize ]; /* filter calculation variables */ + + TQ_UINT8* pel = new TQ_UINT8[ m_pixelSize ]; + Q_CHECK_PTR(pel); + + TQ_UINT8 *pel2 = new TQ_UINT8[ m_pixelSize ]; + Q_CHECK_PTR(pel2); + + bool* bPelDelta = new bool[ m_pixelSize ]; + ContribList *contribX; + ContribList contribY; + const TQ_INT32 BLACK_PIXEL=0; + const TQ_INT32 WHITE_PIXEL=255; + + + // create intermediate row to hold vertical dst row zoom + TQ_UINT8 * tmp = new TQ_UINT8[ width * m_pixelSize ]; + Q_CHECK_PTR(tmp); + + //create array of pointers to intermediate rows + TQ_UINT8 **tmpRows = new TQ_UINT8*[ height ]; + + //create array of pointers to intermediate rows that are actually used simultaneously and allocate memory for the rows + TQ_UINT8 **tmpRowsMem; + if(m_sy < 1.0) + { + tmpRowsMem = new TQ_UINT8*[ (int)(fwidth / m_sy * 2 + 1) ]; + for(int i = 0; i < (int)(fwidth / m_sy * 2 + 1); i++) + { + tmpRowsMem[i] = new TQ_UINT8[ width * m_pixelSize ]; + Q_CHECK_PTR(tmpRowsMem[i]); + } + } + else + { + tmpRowsMem = new TQ_UINT8*[ (int)(fwidth * 2 + 1) ]; + for(int i = 0; i < (int)(fwidth * 2 + 1); i++) + { + tmpRowsMem[i] = new TQ_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); + } + + TQTime starttime = TQTime::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 TQ_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>(tqRound(weight[channel])) : pel[channel]; + tmp[ x * m_pixelSize + channel ] = static_cast<TQ_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>(tqRound(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]; + } + } + + TQTime stoptime = TQTime::currentTime (); + return; +} + +int KisScaleWorker::calcContrib(ContribList *contrib, double scale, double fwidth, int srcwidth, KisFilterStrategy* filterStrategy, TQ_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; + TQ_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/chalk/core/kis_scale_visitor.h b/chalk/core/kis_scale_visitor.h new file mode 100644 index 00000000..afe358ad --- /dev/null +++ b/chalk/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: + TQ_INT32 m_pixel; + double m_weight; + }; + + class ContribList { + public: + TQ_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: + TQ_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, TQ_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: + + TQPtrList<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/chalk/core/kis_selected_transaction.cc b/chalk/core/kis_selected_transaction.cc new file mode 100644 index 00000000..44eec697 --- /dev/null +++ b/chalk/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 TQString& 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/chalk/core/kis_selected_transaction.h b/chalk/core/kis_selected_transaction.h new file mode 100644 index 00000000..c366f336 --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqstring.h> + +#include "kis_transaction.h" + +#include "koffice_export.h" + +class KRITACORE_EXPORT KisSelectedTransaction : public KisTransaction { + typedef KisTransaction super; +public: + KisSelectedTransaction(const TQString& 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/chalk/core/kis_selection.cc b/chalk/core/kis_selection.cc new file mode 100644 index 00000000..e988eff0 --- /dev/null +++ b/chalk/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 <tqimage.h> + +#include <kdebug.h> +#include <klocale.h> +#include <tqcolor.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->tqparentLayer() + , KisMetaRegistry::instance()->csRegistry()->getAlpha8() + , (TQString("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() +{ +} + +TQ_UINT8 KisSelection::selected(TQ_INT32 x, TQ_INT32 y) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + TQ_UINT8 *pix = iter.rawData(); + + return *pix; +} + +void KisSelection::setSelected(TQ_INT32 x, TQ_INT32 y, TQ_UINT8 s) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + + TQ_UINT8 *pix = iter.rawData(); + + *pix = s; +} + +TQImage KisSelection::tqmaskImage() +{ + // If part of a KisAdjustmentLayer, there may be no tqparent device. + TQImage img; + TQRect bounds; + if (m_parentPaintDevice) { + + bounds = m_parentPaintDevice->exactBounds(); + bounds = bounds.intersect( m_parentPaintDevice->image()->bounds() ); + img = TQImage(bounds.width(), bounds.height(), 32); + } + else { + bounds = TQRect( 0, 0, image()->width(), image()->height()); + img = TQImage(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()) { + TQ_UINT8 s = MAX_SELECTED - *(it.rawData()); + TQ_INT32 c = tqRgb(s, s, s); + img.setPixel(x2, y2, c); + ++x2; + ++it; + } + it.nextRow(); + } + return img; +} +void KisSelection::select(TQRect r) +{ + KisFillPainter painter(this); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + painter.fillRect(r, KisColor(TQt::white, cs), MAX_SELECTED); + TQ_INT32 x, y, w, h; + extent(x, y, w, h); +} + +void KisSelection::clear(TQRect r) +{ + KisFillPainter painter(this); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + painter.fillRect(r, KisColor(TQt::white, cs), MIN_SELECTED); +} + +void KisSelection::clear() +{ + TQ_UINT8 defPixel = MIN_SELECTED; + m_datamanager->setDefaultPixel(&defPixel); + m_datamanager->clear(); +} + +void KisSelection::invert() +{ + TQ_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; + } + TQ_UINT8 defPixel = MAX_SELECTED - *(m_datamanager->defaultPixel()); + m_datamanager->setDefaultPixel(&defPixel); +} + +bool KisSelection::isTotallyUnselected(TQRect r) +{ + if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) + return false; + TQRect sr = selectedExactRect(); + return ! r.intersects(sr); +} + +bool KisSelection::isProbablyTotallyUnselected(TQRect r) +{ + if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) + return false; + TQRect sr = selectedRect(); + return ! r.intersects(sr); +} + + +TQRect KisSelection::selectedRect() const +{ + if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) + return extent(); + else + return extent().unite(m_parentPaintDevice->extent()); +} + +TQRect 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(TQImage img, const TQRect& imageRect, const TQRegion& uniformRegion) +{ + Q_ASSERT(img.size() == imageRect.size()); + Q_ASSERT(imageRect.tqcontains(uniformRegion.boundingRect())); + + if (img.isNull() || img.size() != imageRect.size() || !imageRect.tqcontains(uniformRegion.boundingRect())) { + return; + } + + if (*m_datamanager->defaultPixel() == MIN_SELECTED) { + + TQRegion region = uniformRegion & TQRegion(imageRect); + + if (!region.isEmpty()) { + TQMemArray<TQRect> rects = region.tqrects(); + + for (unsigned int i = 0; i < rects.count(); i++) { + TQRect r = rects[i]; + + for (TQ_INT32 y = 0; y < r.height(); ++y) { + + TQRgb *imagePixel = reinterpret_cast<TQRgb *>(img.scanLine(r.y() - imageRect.y() + y)); + imagePixel += r.x() - imageRect.x(); + + TQ_INT32 numPixels = r.width(); + + while (numPixels > 0) { + + TQRgb srcPixel = *imagePixel; + TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; + TQ_UINT8 srcAlpha = tqAlpha(srcPixel); + + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + TQ_UINT8 dstAlpha = TQMAX(srcAlpha, 192); + + TQRgb dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + *imagePixel = dstPixel; + + ++imagePixel; + --numPixels; + } + } + } + } + } +} + +void KisSelection::paintSelection(TQImage img, TQ_INT32 imageRectX, TQ_INT32 imageRectY, TQ_INT32 imageRectWidth, TQ_INT32 imageRectHeight) +{ + Q_ASSERT(img.size() == TQSize(imageRectWidth, imageRectHeight)); + + if (img.isNull() || img.size() != TQSize(imageRectWidth, imageRectHeight)) { + return; + } + + TQRect imageRect(imageRectX, imageRectY, imageRectWidth, imageRectHeight); + TQRect selectionExtent = extent(); + + selectionExtent.setLeft(selectionExtent.left() - 1); + selectionExtent.setTop(selectionExtent.top() - 1); + selectionExtent.setWidth(selectionExtent.width() + 2); + selectionExtent.setHeight(selectionExtent.height() + 2); + + TQRegion uniformRegion = TQRegion(imageRect); + uniformRegion -= TQRegion(selectionExtent); + + if (!uniformRegion.isEmpty()) { + paintUniformSelectionRegion(img, imageRect, uniformRegion); + } + + TQRect nonuniformRect = imageRect & selectionExtent; + + if (!nonuniformRect.isEmpty()) { + + const TQ_INT32 imageRectOffsetX = nonuniformRect.x() - imageRectX; + const TQ_INT32 imageRectOffsetY = nonuniformRect.y() - imageRectY; + + imageRectX = nonuniformRect.x(); + imageRectY = nonuniformRect.y(); + imageRectWidth = nonuniformRect.width(); + imageRectHeight = nonuniformRect.height(); + + const TQ_INT32 NUM_SELECTION_ROWS = 3; + + TQ_UINT8 *selectionRow[NUM_SELECTION_ROWS]; + + TQ_INT32 aboveRowIndex = 0; + TQ_INT32 centreRowIndex = 1; + TQ_INT32 belowRowIndex = 2; + + selectionRow[aboveRowIndex] = new TQ_UINT8[imageRectWidth + 2]; + selectionRow[centreRowIndex] = new TQ_UINT8[imageRectWidth + 2]; + selectionRow[belowRowIndex] = new TQ_UINT8[imageRectWidth + 2]; + + readBytes(selectionRow[centreRowIndex], imageRectX - 1, imageRectY - 1, imageRectWidth + 2, 1); + readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY, imageRectWidth + 2, 1); + + for (TQ_INT32 y = 0; y < imageRectHeight; ++y) { + + TQ_INT32 oldAboveRowIndex = aboveRowIndex; + aboveRowIndex = centreRowIndex; + centreRowIndex = belowRowIndex; + belowRowIndex = oldAboveRowIndex; + + readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY + y + 1, imageRectWidth + 2, 1); + + const TQ_UINT8 *aboveRow = selectionRow[aboveRowIndex] + 1; + const TQ_UINT8 *centreRow = selectionRow[centreRowIndex] + 1; + const TQ_UINT8 *belowRow = selectionRow[belowRowIndex] + 1; + + TQRgb *imagePixel = reinterpret_cast<TQRgb *>(img.scanLine(imageRectOffsetY + y)); + imagePixel += imageRectOffsetX; + + for (TQ_INT32 x = 0; x < imageRectWidth; ++x) { + + TQ_UINT8 centre = *centreRow; + + if (centre != MAX_SELECTED) { + + // this is where we come if the pixels should be blue or bluish + + TQRgb srcPixel = *imagePixel; + TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; + TQ_UINT8 srcAlpha = tqAlpha(srcPixel); + + // Colour influence is proportional to alphaPixel. + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + + TQRgb dstPixel; + + if (centre == MIN_SELECTED) { + //this is where we come if the pixels should be blue (or red outline) + + TQ_UINT8 left = *(centreRow - 1); + TQ_UINT8 right = *(centreRow + 1); + TQ_UINT8 above = *aboveRow; + TQ_UINT8 below = *belowRow; + + // Stop unselected transparent areas from appearing the same + // as selected transparent areas. + TQ_UINT8 dstAlpha = TQMAX(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 = tqRgba(255, 0, 0, dstAlpha); + } else { + dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + } + } else { + dstPixel = tqRgba(UINT8_BLEND(tqRed(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(tqGreen(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(tqBlue(srcPixel), srcGrey + 165, centre), + srcAlpha); + } + + *imagePixel = dstPixel; + } + + aboveRow++; + centreRow++; + belowRow++; + imagePixel++; + } + } + + delete [] selectionRow[aboveRowIndex]; + delete [] selectionRow[centreRowIndex]; + delete [] selectionRow[belowRowIndex]; + } +} + +void KisSelection::paintSelection(TQImage img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize) +{ + if (img.isNull() || scaledImageRect.isEmpty() || scaledImageSize.isEmpty() || imageSize.isEmpty()) { + return; + } + + Q_ASSERT(img.size() == scaledImageRect.size()); + + if (img.size() != scaledImageRect.size()) { + return; + } + + TQ_INT32 imageWidth = imageSize.width(); + TQ_INT32 imageHeight = imageSize.height(); + + TQRect 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; + + TQRect 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); + + TQRegion uniformRegion = TQRegion(scaledImageRect); + uniformRegion -= TQRegion(scaledSelectionExtent); + + if (!uniformRegion.isEmpty()) { + paintUniformSelectionRegion(img, scaledImageRect, uniformRegion); + } + + TQRect nonuniformRect = scaledImageRect & scaledSelectionExtent; + + if (!nonuniformRect.isEmpty()) { + + const TQ_INT32 scaledImageRectXOffset = nonuniformRect.x() - scaledImageRect.x(); + const TQ_INT32 scaledImageRectYOffset = nonuniformRect.y() - scaledImageRect.y(); + + const TQ_INT32 scaledImageRectX = nonuniformRect.x(); + const TQ_INT32 scaledImageRectY = nonuniformRect.y(); + const TQ_INT32 scaledImageRectWidth = nonuniformRect.width(); + const TQ_INT32 scaledImageRectHeight = nonuniformRect.height(); + + const TQ_INT32 imageRowLeft = static_cast<TQ_INT32>(scaledImageRectX / xScale); + const TQ_INT32 imageRowRight = static_cast<TQ_INT32>((ceil((scaledImageRectX + scaledImageRectWidth - 1 + 1) / xScale)) - 1); + + const TQ_INT32 imageRowWidth = imageRowRight - imageRowLeft + 1; + const TQ_INT32 imageRowStride = imageRowWidth + 2; + + const TQ_INT32 NUM_SELECTION_ROWS = 3; + + TQ_INT32 aboveRowIndex = 0; + TQ_INT32 centreRowIndex = 1; + TQ_INT32 belowRowIndex = 2; + + TQ_INT32 aboveRowSrcY = -3; + TQ_INT32 centreRowSrcY = -3; + TQ_INT32 belowRowSrcY = -3; + + TQ_UINT8 *selectionRows = new TQ_UINT8[imageRowStride * NUM_SELECTION_ROWS]; + TQ_UINT8 *selectionRow[NUM_SELECTION_ROWS]; + + selectionRow[0] = selectionRows + 1; + selectionRow[1] = selectionRow[0] + imageRowStride; + selectionRow[2] = selectionRow[0] + (2 * imageRowStride); + + for (TQ_INT32 y = 0; y < scaledImageRectHeight; ++y) { + + TQ_INT32 scaledY = scaledImageRectY + y; + TQ_INT32 srcY = (scaledY * imageHeight) / scaledImageSize.height(); + + TQ_UINT8 *aboveRow; + TQ_UINT8 *centreRow; + TQ_UINT8 *belowRow; + + if (srcY - 1 == aboveRowSrcY) { + aboveRow = selectionRow[aboveRowIndex]; + centreRow = selectionRow[centreRowIndex]; + belowRow = selectionRow[belowRowIndex]; + } else if (srcY - 1 == centreRowSrcY) { + + TQ_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) { + + TQ_INT32 oldAboveRowIndex = aboveRowIndex; + TQ_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; + + TQRgb *imagePixel = reinterpret_cast<TQRgb *>(img.scanLine(scaledImageRectYOffset + y)); + imagePixel += scaledImageRectXOffset; + + for (TQ_INT32 x = 0; x < scaledImageRectWidth; ++x) { + + TQ_INT32 scaledX = scaledImageRectX + x; + TQ_INT32 srcX = (scaledX * imageWidth) / scaledImageSize.width(); + + TQ_UINT8 centre = *(centreRow + srcX - imageRowLeft); + + if (centre != MAX_SELECTED) { + + // this is where we come if the pixels should be blue or bluish + + TQRgb srcPixel = *imagePixel; + TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; + TQ_UINT8 srcAlpha = tqAlpha(srcPixel); + + // Colour influence is proportional to alphaPixel. + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + + TQRgb dstPixel; + + if (centre == MIN_SELECTED) { + //this is where we come if the pixels should be blue (or red outline) + + TQ_UINT8 left = *(centreRow + (srcX - imageRowLeft) - 1); + TQ_UINT8 right = *(centreRow + (srcX - imageRowLeft) + 1); + TQ_UINT8 above = *(aboveRow + (srcX - imageRowLeft)); + TQ_UINT8 below = *(belowRow + (srcX - imageRowLeft)); + + // Stop unselected transparent areas from appearing the same + // as selected transparent areas. + TQ_UINT8 dstAlpha = TQMAX(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 = tqRgba(255, 0, 0, dstAlpha); + } else { + dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + } + } else { + dstPixel = tqRgba(UINT8_BLEND(tqRed(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(tqGreen(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(tqBlue(srcPixel), srcGrey + 165, centre), + srcAlpha); + } + + *imagePixel = dstPixel; + } + + imagePixel++; + } + } + + delete [] selectionRows; + } +} + +void KisSelection::setDirty(const TQRect& rc) +{ + if (m_dirty) + super::setDirty(rc); +} + +void KisSelection::setDirty() +{ + if (m_dirty) + super::setDirty(); +} diff --git a/chalk/core/kis_selection.h b/chalk/core/kis_selection.h new file mode 100644 index 00000000..f41915da --- /dev/null +++ b/chalk/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 <tqrect.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 tqparent paint device. The selection will never be bigger than the tqparent + * paint device. + */ + KisSelection(KisPaintDeviceSP dev); + + /** + * Create a new KisSelection. This selection will not have a tqparent paint device. + */ + KisSelection(); + + /** + * Copy the selection + */ + KisSelection(const KisSelection& rhs); + + virtual ~KisSelection(); + + // Returns selectedness, or 0 if invalid coordinates + TQ_UINT8 selected(TQ_INT32 x, TQ_INT32 y); + + void setSelected(TQ_INT32 x, TQ_INT32 y, TQ_UINT8 s); + + TQImage tqmaskImage(); + + void select(TQRect r); + + void invert(); + + void clear(TQRect r); + + void clear(); + + /// Tests if the the rect is totally outside the selection + bool isTotallyUnselected(TQRect 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(TQRect r); + + /** + * Rough, but fastish way of determining the area + * of the tiles used by the selection. + */ + TQRect selectedRect() const; + + /** + * Slow, but exact way of determining the rectangle + * that encloses the selection + */ + TQRect selectedExactRect() const; + + void paintSelection(TQImage img, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + void paintSelection(TQImage img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize); + + void startCachingExactRect(); + void stopCachingExactRect(); + + // if the tqparent 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 TQRect & rc); + virtual void setDirty(); + inline KisPaintDeviceSP tqparentPaintDevice() { return m_parentPaintDevice; } +private: + void paintUniformSelectionRegion(TQImage img, const TQRect& imageRect, const TQRegion& uniformRegion); + +private: + + // We don't want these methods to be used on selections: + void extent(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const + { + KisPaintDevice::extent(x,y,w,h); + } + + TQRect extent() const { return KisPaintDevice::extent(); } + + void exactBounds(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const + { + return KisPaintDevice::exactBounds(x,y,w,h); + } + + TQRect exactBounds() const + { + return KisPaintDevice::exactBounds(); + } + + TQRect exactBoundsOldMethod() const + { + return KisPaintDevice::exactBoundsOldMethod(); + } + + TQRect exactBoundsImprovedOldMethod() const + { + return KisPaintDevice::exactBoundsImprovedOldMethod(); + } + + +private: + KisPaintDeviceSP m_parentPaintDevice; + bool m_doCacheExactRect; + TQRect m_cachedExactRect; + bool m_dirty; +}; + +#endif // KIS_SELECTION_H_ diff --git a/chalk/core/kis_shear_visitor.h b/chalk/core/kis_shear_visitor.h new file mode 100644 index 00000000..9a48181e --- /dev/null +++ b/chalk/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/chalk/core/kis_strategy_move.cc b/chalk/core/kis_strategy_move.cc new file mode 100644 index 00000000..7391777c --- /dev/null +++ b/chalk/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 <tqpoint.h> +#include <tqcolor.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 TQPoint& 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 TQPoint& 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())) { + TQPoint pos = original; + TQRect 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 = TQPoint(dev->x(), dev->y()); + m_dragStart = original; + + dev->setDirty(rc); + } + } +} + +void KisStrategyMove::endDrag(const TQPoint& 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 TQPoint& pt1, const TQPoint& pt2) +{ + startDrag(pt1); + endDrag(pt2); +} + +void KisStrategyMove::simpleMove(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 x2, TQ_INT32 y2) +{ + startDrag(TQPoint(x1, y1)); + endDrag(TQPoint(x2, y2)); +} + diff --git a/chalk/core/kis_strategy_move.h b/chalk/core/kis_strategy_move.h new file mode 100644 index 00000000..c3535be9 --- /dev/null +++ b/chalk/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 <tqpoint.h> +#include <tqrect.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 TQPoint& pos); + void drag(const TQPoint& pos); + void endDrag(const TQPoint& pos, bool undo = true); + void simpleMove(const TQPoint& pt1, const TQPoint& pt2); + void simpleMove(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 x2, TQ_INT32 y2); + +private: + KisStrategyMove(const KisStrategyMove&); + KisStrategyMove& operator=(const KisStrategyMove&); + +private: + KisCanvasController *m_controller; + KisCanvasSubject *m_subject; + TQRect m_deviceBounds; + TQPoint m_dragStart; + TQPoint m_layerStart; + TQPoint m_layerPosition; + bool m_dragging; +}; + +#endif // KIS_STRATEGY_MOVE_H_ + diff --git a/chalk/core/kis_substrate.h b/chalk/core/kis_substrate.h new file mode 100644 index 00000000..274f2680 --- /dev/null +++ b/chalk/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 <tqrect.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? + TQ_UINT8 r; //.Red component of reflectivity + TQ_UINT8 g; // Green component of reflectivity + TQ_UINT8 b; // Blue component of reflectivity + TQ_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 TQRect & 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 TQRect & 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/chalk/core/kis_thread.h b/chalk/core/kis_thread.h new file mode 100644 index 00000000..61feda24 --- /dev/null +++ b/chalk/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 <tqthread.h> +#include <ksharedptr.h> + +/** + * A KisThread is a TQThread 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 TQThread { + +public: + + /** + * Create a new KisThread with the canceled state set to false + */ + KisThread() : TQThread(), 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/chalk/core/kis_thread_pool.cc b/chalk/core/kis_thread_pool.cc new file mode 100644 index 00000000..e617c8cb --- /dev/null +++ b/chalk/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.tqfindRef(thread); + if (i >= 0) { + t = m_threads.take(i); + m_numberOfQueuedThreads--; + } else { + i = m_runningThreads.tqfindRef(thread); + if (i >= 0) { + t = m_runningThreads.take(i); + m_numberOfRunningThreads--; + } + else { + i = m_oldThreads.tqfindRef(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/chalk/core/kis_thread_pool.h b/chalk/core/kis_thread_pool.h new file mode 100644 index 00000000..904606bf --- /dev/null +++ b/chalk/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 <tqthread.h> +#include <tqptrlist.h> +#include <tqmutex.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&); + + TQMutex m_poolMutex; + int m_numberOfRunningThreads; + int m_numberOfQueuedThreads; + int m_maxThreads; + int m_wait; + TQPtrList<KisThread> m_threads; + TQPtrList<KisThread> m_runningThreads; + TQPtrList<KisThread> m_oldThreads; + + static KisThreadPool * m_singleton; +}; + + +#endif diff --git a/chalk/core/kis_transaction.cc b/chalk/core/kis_transaction.cc new file mode 100644 index 00000000..b755c597 --- /dev/null +++ b/chalk/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: + TQString m_name; + KisPaintDeviceSP m_device; + KisMementoSP m_memento; + +}; + +KisTransaction::KisTransaction(const TQString& 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); + + TQRect rc; + TQ_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->tqparentLayer(); + if (l) l->setDirty(rc); +} + +void KisTransaction::unexecute() +{ + Q_ASSERT(m_private->m_memento != 0); + m_private->m_device->rollback(m_private->m_memento); + + TQRect rc; + TQ_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->tqparentLayer(); + if (l) l->setDirty(rc); + +} + +void KisTransaction::unexecuteNoUpdate() +{ + Q_ASSERT(m_private->m_memento != 0); + + m_private->m_device->rollback(m_private->m_memento); +} + +TQString KisTransaction::name() const +{ + return m_private->m_name; +} diff --git a/chalk/core/kis_transaction.h b/chalk/core/kis_transaction.h new file mode 100644 index 00000000..521b0204 --- /dev/null +++ b/chalk/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 <tqstring.h> + +#include <kcommand.h> + +#include "kis_types.h" + +class TQRect; +class KisTransactionPrivate; + +class KisTransaction : public KCommand { +public: + KisTransaction(const TQString& name, KisPaintDeviceSP device); + virtual ~KisTransaction(); + +public: + virtual void execute(); + virtual void unexecute(); + virtual void unexecuteNoUpdate(); + virtual TQString name() const; +private: + KisTransactionPrivate * m_private; +}; + +#endif // KIS_TILE_COMMAND_H_ + diff --git a/chalk/core/kis_transform_visitor.h b/chalk/core/kis_transform_visitor.h new file mode 100644 index 00000000..9e6f51c0 --- /dev/null +++ b/chalk/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 "tqrect.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, + TQ_INT32 tx, TQ_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; + TQ_INT32 m_tx, m_ty; + KisFilterStrategy *m_filter; + double m_angle; + KisProgressDisplayInterface *m_progress; + KisImageSP m_img; +}; + + +#endif diff --git a/chalk/core/kis_transform_worker.cc b/chalk/core/kis_transform_worker.cc new file mode 100644 index 00000000..fbc952ea --- /dev/null +++ b/chalk/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, + TQ_INT32 xtranslate, TQ_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; + TQ_INT32 pixelSize = src->pixelSize(); + TQRect 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 (TQ_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; + TQ_INT32 pixelSize = src->pixelSize(); + TQRect 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 (TQ_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; + TQ_INT32 pixelSize = src->pixelSize(); + TQRect 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 + } + TQ_INT32 x = 0; + + for (TQ_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; + TQ_INT32 pixelSize = src->pixelSize(); + TQRect 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 (TQ_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, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len); + +template <> KisHLineIteratorPixel createIterator <KisHLineIteratorPixel> +(KisPaintDevice *dev, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len) +{ + return dev->createHLineIterator(start, lineNum, len, true); +} + +template <> KisVLineIteratorPixel createIterator <KisVLineIteratorPixel> +(KisPaintDevice *dev, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len) +{ + return dev->createVLineIterator(lineNum, start, len, true); +} + +template <class iter> void calcDimensions (KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines); + +template <> void calcDimensions <KisHLineIteratorPixel> +(KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines) +{ + if(dev->hasSelection()) + { + TQRect r = dev->selection()->selectedExactRect(); + r.rect(&srcStart, &firstLine, &srcLen, &numLines); + } + else + dev->exactBounds(srcStart, firstLine, srcLen, numLines); +} + +template <> void calcDimensions <KisVLineIteratorPixel> +(KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines) +{ + if(dev->hasSelection()) + { + TQRect r = dev->selection()->selectedExactRect(); + r.rect(&firstLine, &srcStart, &numLines, &srcLen); + } + else + dev->exactBounds(firstLine, srcStart, numLines, srcLen); +} + +struct FilterValues +{ + TQ_UINT8 numWeights; + TQ_UINT8 *weight; + ~FilterValues() {delete [] weight;} +}; + +template <class T> void KisTransformWorker::transformPass(KisPaintDevice *src, KisPaintDevice *dst, double floatscale, double shear, TQ_INT32 dx, KisFilterStrategy *filterStrategy, bool fixBorderAlpha) +{ + TQ_INT32 lineNum,srcStart,firstLine,srcLen,numLines; + TQ_INT32 center, begin, end; /* filter calculation variables */ + TQ_UINT8 *data; + TQ_UINT8 pixelSize = src->pixelSize(); + KisSelectionSP dstSelection; + KisColorSpace * cs = src->colorSpace(); + TQ_INT32 scale; + TQ_INT32 scaleDenom; + TQ_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; + + TQ_INT32 support = filterStrategy->intSupport(); + TQ_INT32 dstLen, dstStart; + TQ_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 + TQ_INT32 extraLen = (support+256)>>8 + 1; + + TQ_UINT8 *tmpLine = new TQ_UINT8[(srcLen +2*extraLen)* pixelSize]; + Q_CHECK_PTR(tmpLine); + + TQ_UINT8 *tmpSel = new TQ_UINT8[srcLen+2*extraLen]; + Q_CHECK_PTR(tmpSel); + + //allocate space for colors + const TQ_UINT8 **colors = new const TQ_UINT8 *[2*support+1]; + + // Precalculate weights + FilterValues *filterWeights = new FilterValues[256]; + + for(int center = 0; center<256; ++center) + { + TQ_INT32 begin = (255 + center - support)>>8; // takes ceiling by adding 255 + TQ_INT32 span = ((center + support)>>8) - begin + 1; // takes floor to get end. Subtracts begin to get span + TQ_INT32 t = (((begin<<8) - center) * invfscale)>>8; + TQ_INT32 dt = invfscale; + filterWeights[center].weight = new TQ_UINT8[span]; +//printf("%d (",center); + TQ_UINT32 sum=0; + for(int num = 0; num<span; ++num) + { + TQ_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); + TQ_INT32 i = 0; + while(!srcIt.isDone()) + { + TQ_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); + TQ_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; + TQRect 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; + TQ_INT32 xtranslate = m_xtranslate; + TQ_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->tqparentLayer()) { + m_dev->tqparentLayer()->setDirty(); + } + //progress info + emit notifyProgressDone(); + m_dev->emitSelectionChanged(); + + return m_cancelRequested; +} diff --git a/chalk/core/kis_transform_worker.h b/chalk/core/kis_transform_worker.h new file mode 100644 index 00000000..a48b8589 --- /dev/null +++ b/chalk/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, + TQ_INT32 xtranslate, TQ_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, TQ_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; + TQ_INT32 m_xtranslate, m_ytranslate; + KisProgressDisplayInterface *m_progress; + KisFilterStrategy *m_filter; + // Implement KisProgressSubject + bool m_cancelRequested; + virtual void cancel() { m_cancelRequested = true; } + TQ_INT32 m_progressTotalSteps; + TQ_INT32 m_progressStep; + TQ_INT32 m_progressScaler; + TQ_INT32 m_lastProgressReport; + bool m_fixBorderAlpha; +}; + + +inline KisTransformWorker::~KisTransformWorker() +{ +} + +#endif // KIS_TRANSFORM_VISITOR_H_ diff --git a/chalk/core/kis_types.h b/chalk/core/kis_types.h new file mode 100644 index 00000000..b789fdd4 --- /dev/null +++ b/chalk/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 <tqvaluevector.h> +#include <tqmap.h> +#include <tqpoint.h> + +#include <ksharedptr.h> + +#include "kis_shared_ptr_vector.h" + +/** + * Define lots of shared pointer versions of Chalk 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 TQValueVector<TQPoint> 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/chalk/core/kis_vec.cc b/chalk/core/kis_vec.cc new file mode 100644 index 00000000..fa54e1f9 --- /dev/null +++ b/chalk/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/chalk/core/kis_vec.h b/chalk/core/kis_vec.h new file mode 100644 index 00000000..08b9c541 --- /dev/null +++ b/chalk/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 <tqpoint.h> +#include "kis_point.h" + +/* + * vector classes + */ +const double epsilon = DBL_EPSILON; + +class KisVector2D +{ +public: + KisVector2D(); + KisVector2D(double x, double y); + KisVector2D(const TQPoint& 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 TQPoint& 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/chalk/core/tests/Makefile.am b/chalk/core/tests/Makefile.am new file mode 100644 index 00000000..239c1ba6 --- /dev/null +++ b/chalk/core/tests/Makefile.am @@ -0,0 +1,30 @@ +AM_CPPFLAGS = \ + -I$(srcdir)/../ \ + -I$(srcdir)/../tiles \ + -I$(srcdir)/../../sdk \ + -I$(srcdir)/../../chalkcolor \ + -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 ../../libchalkcommon.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 ../../libchalkcommon.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 ../../libchalkcommon.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/chalk/core/tests/kis_filter_configuration_tester.cc b/chalk/core/tests/kis_filter_configuration_tester.cc new file mode 100644 index 00000000..6a4a2631 --- /dev/null +++ b/chalk/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 <tqapplication.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(), TQString("test")); + + delete kfc; + success("testCreation success"); +} + +void KisFilterConfigurationTester::testRoundTrip() +{ + KisFilterConfiguration * kfc = new KisFilterConfiguration("test", 1); + CHECK(kfc->version(), 1); + CHECK(kfc->name(), TQString("test")); + TQString s = kfc->toString(); + delete kfc; + kfc = new KisFilterConfiguration(s); + CHECK(kfc->version(), 1); + CHECK(kfc->name(), TQString("test")); + delete kfc; + success("testDeserializaton success"); +} + +void KisFilterConfigurationTester::testSetGetProperty() +{ +} diff --git a/chalk/core/tests/kis_filter_configuration_tester.h b/chalk/core/tests/kis_filter_configuration_tester.h new file mode 100644 index 00000000..6e1ca2e5 --- /dev/null +++ b/chalk/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/chalk/core/tests/kis_image_tester.cpp b/chalk/core/tests/kis_image_tester.cpp new file mode 100644 index 00000000..f660f394 --- /dev/null +++ b/chalk/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 <tqapplication.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); + + TQColor colour; + TQ_UINT8 opacity; + + mergedPixel.toTQColor(&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, TQColor(255, 128, 64), OPACITY_OPAQUE); + + mergedPixel = image->mergedPixel(0, 0); + mergedPixel.toTQColor(&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, TQColor(255, 255, 255), OPACITY_OPAQUE); + + mergedPixel = image->mergedPixel(0, 0); + mergedPixel.toTQColor(&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/chalk/core/tests/kis_image_tester.h b/chalk/core/tests/kis_image_tester.h new file mode 100644 index 00000000..4e676838 --- /dev/null +++ b/chalk/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/chalk/core/tests/kis_integer_maths_tester.cpp b/chalk/core/tests/kis_integer_maths_tester.cpp new file mode 100644 index 00000000..1878731b --- /dev/null +++ b/chalk/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/chalk/core/tests/kis_integer_maths_tester.h b/chalk/core/tests/kis_integer_maths_tester.h new file mode 100644 index 00000000..2a9a9a20 --- /dev/null +++ b/chalk/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/chalk/core/tiles/Makefile.am b/chalk/core/tiles/Makefile.am new file mode 100644 index 00000000..1e334ab4 --- /dev/null +++ b/chalk/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 = libchalktile.la + +libchalktile_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 + +libchalktile_la_METASOURCES = AUTO + +include_HEADERS = \ + kis_tileddatamanager.h +noinst_HEADERS = kis_tiled_random_accessor.h diff --git a/chalk/core/tiles/kis_memento.cc b/chalk/core/tiles/kis_memento.cc new file mode 100644 index 00000000..aaec3724 --- /dev/null +++ b/chalk/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(TQ_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 TQ_UINT8[pixelSize]; + m_redoDefPixel = new TQ_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(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const +{ + TQ_INT32 maxX = TQ_INT32_MIN; + TQ_INT32 maxY = TQ_INT32_MIN; + x = TQ_INT32_MAX; + y = TQ_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; +} + +TQRect KisMemento::extent() const +{ + TQ_INT32 x; + TQ_INT32 y; + TQ_INT32 w; + TQ_INT32 h; + + extent(x, y, w, h); + + return TQRect(x, y, w, h); +} + +bool KisMemento::containsTile(TQ_INT32 col, TQ_INT32 row, TQ_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/chalk/core/tiles/kis_memento.h b/chalk/core/tiles/kis_memento.h new file mode 100644 index 00000000..5942dc44 --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqrect.h> + +#include <ksharedptr.h> + +class KisTile; +class KisTiledDataManager; + +class KisMemento; +typedef KSharedPtr<KisMemento> KisMementoSP; + +class KisMemento : public KShared +{ +public: + KisMemento(TQ_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(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const; + TQRect extent() const; + + bool containsTile(TQ_INT32 col, TQ_INT32 row, TQ_UINT32 tileHash) const; + + // For debugging use + bool valid() const { return m_valid; } + void setInvalid() { m_valid = false; } + +private: + + class DeletedTile { + public: + DeletedTile(TQ_INT32 col, TQ_INT32 row, const DeletedTile *next) + : m_col(col), + m_row(row), + m_next(next) + { + } + + TQ_INT32 col() const { return m_col; } + TQ_INT32 row() const { return m_row; } + const DeletedTile *next() const { return m_next; } + + private: + TQ_INT32 m_col; + TQ_INT32 m_row; + const DeletedTile *m_next; + }; + + class DeletedTileList { + public: + DeletedTileList() + : m_firstDeletedTile(0) + { + } + + ~DeletedTileList(); + + void addTile(TQ_INT32 col, TQ_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(TQ_INT32 col, TQ_INT32 row) + { + m_redoDelTilesList.addTile(col, row); + } + + DeletedTile *tileListToDeleteOnRedo() + { + return m_redoDelTilesList.firstTile(); + } + + void clearTilesToDeleteOnRedo() + { + m_redoDelTilesList.clear(); + } + + void addTileToDeleteOnUndo(TQ_INT32 col, TQ_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; + TQ_UINT32 m_numTiles; + KisTile **m_redoHashTable; + DeletedTileList m_redoDelTilesList; + DeletedTileList m_undoDelTilesList; + TQ_UINT8 *m_defPixel; + TQ_UINT8 *m_redoDefPixel; + void deleteAll(KisTile *tile); + + bool m_valid; +}; + +#endif // KIS_MEMENTO_H_ diff --git a/chalk/core/tiles/kis_tile.cc b/chalk/core/tiles/kis_tile.cc new file mode 100644 index 00000000..86d55128 --- /dev/null +++ b/chalk/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 TQ_INT32 KisTile::WIDTH = 64; +const TQ_INT32 KisTile::HEIGHT = 64; + + +KisTile::KisTile(TQ_INT32 pixelSize, TQ_INT32 col, TQ_INT32 row, const TQ_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, TQ_INT32 col, TQ_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(TQ_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(TQ_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; +} + +TQ_UINT8 *KisTile::data(TQ_INT32 x, TQ_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 TQ_UINT8 *pixel) +{ + addReader(); + TQ_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/chalk/core/tiles/kis_tile.h b/chalk/core/tiles/kis_tile.h new file mode 100644 index 00000000..f2d6f1e9 --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqrect.h> + +class KisTiledDataManager; +class KisTiledIterator; + +/** + * Provides abstraction to a tile. A tile tqcontains + * a part of a PaintDevice, but only the individual pixels + * are accesable and that only via iterators. + */ +class KisTile { +public: + KisTile(TQ_INT32 pixelSize, TQ_INT32 col, TQ_INT32 row, const TQ_UINT8 *defPixel); + KisTile(const KisTile& rhs, TQ_INT32 col, TQ_INT32 row); + KisTile(const KisTile& rhs); + ~KisTile(); + +public: + void release(); + void allocate(); + + TQ_UINT8 *data(TQ_INT32 xoff, TQ_INT32 yoff) const; + TQ_UINT8 *data() const { return m_data; } + + void setData(const TQ_UINT8 *pixel); + + TQ_INT32 refCount() const; + void ref(); + + TQ_INT32 getRow() const { return m_row; } + TQ_INT32 getCol() const { return m_col; } + + TQRect extent() const { return TQRect(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; + TQ_INT32 readers() { return m_nReadlock; } + + friend class KisTiledIterator; + friend class KisTiledDataManager; + friend class KisMemento; + friend class KisTileManager; +private: + KisTile& operator=(const KisTile&); + +private: + TQ_UINT8 *m_data; + mutable TQ_INT32 m_nReadlock; + TQ_INT32 m_row; + TQ_INT32 m_col; + TQ_INT32 m_pixelSize; + KisTile *m_nextTile; + +public: + static const TQ_INT32 WIDTH; + static const TQ_INT32 HEIGHT; +}; + +#endif // KIS_TILE_H_ + diff --git a/chalk/core/tiles/kis_tile_global.h b/chalk/core/tiles/kis_tile_global.h new file mode 100644 index 00000000..93052a4f --- /dev/null +++ b/chalk/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/chalk/core/tiles/kis_tiled_random_accessor.cc b/chalk/core/tiles/kis_tiled_random_accessor.cc new file mode 100644 index 00000000..159faf18 --- /dev/null +++ b/chalk/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 TQ_UINT32 KisTiledRandomAccessor::CACHESIZE = 4; // Define the number of tiles we keep in cache + +KisTiledRandomAccessor::KisTiledRandomAccessor(KisTiledDataManager *ktm, TQ_INT32 x, TQ_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(TQ_INT32 x, TQ_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]; + TQ_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++; + } + TQ_UINT32 col = xToCol( x ); + TQ_UINT32 row = yToRow( y ); + KisTileInfo* kti = fetchTileData(col, row); + TQ_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; +} + + +TQ_UINT8 * KisTiledRandomAccessor::rawData() const +{ + return m_data; +} + + +const TQ_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(TQ_INT32 col, TQ_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/chalk/core/tiles/kis_tiled_random_accessor.h b/chalk/core/tiles/kis_tiled_random_accessor.h new file mode 100644 index 00000000..23768ee6 --- /dev/null +++ b/chalk/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 <tqrect.h> +#include <tqvaluelist.h> + +#include <ksharedptr.h> + +#include <kis_tileddatamanager.h> + +class KisTile; + +class KisTiledRandomAccessor : public KShared { + struct KisTileInfo { + KisTile* tile; + KisTile* oldtile; + TQ_UINT8* data; + const TQ_UINT8* oldData; + TQ_INT32 area_x1, area_y1, area_x2, area_y2; + }; + public: + KisTiledRandomAccessor(KisTiledDataManager *ktm, TQ_INT32 x, TQ_INT32 y, bool writable); + ~KisTiledRandomAccessor(); + + + private: + inline TQ_UINT32 xToCol(TQ_UINT32 x) const { if (m_ktm) return m_ktm->xToCol(x); else return 0; }; + inline TQ_UINT32 yToRow(TQ_UINT32 y) const { if (m_ktm) return m_ktm->yToRow(y); else return 0; }; + KisTileInfo* fetchTileData(TQ_INT32 col, TQ_INT32 row); + + public: + /// Move to a given x,y position, fetch tiles and data + void moveTo(TQ_INT32 x, TQ_INT32 y); + TQ_UINT8* rawData() const; + const TQ_UINT8* oldRawData() const; + + private: + KisTiledDataManager *m_ktm; + KisTileInfo** m_tilesCache; + TQ_UINT32 m_tilesCacheSize; + TQ_INT32 m_pixelSize; + TQ_UINT8* m_data; + const TQ_UINT8* m_oldData; + bool m_writable; + static const TQ_UINT32 CACHESIZE; // Define the number of tiles we keep in cache + +}; + +#endif diff --git a/chalk/core/tiles/kis_tileddatamanager.cc b/chalk/core/tiles/kis_tileddatamanager.cc new file mode 100644 index 00000000..629b1b38 --- /dev/null +++ b/chalk/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 <tqvaluevector.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(TQ_UINT32 pixelSize, const TQ_UINT8 *defPixel) +{ + m_pixelSize = pixelSize; + + m_defPixel = new TQ_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 = TQ_INT32_MAX; + m_extentMinY = TQ_INT32_MAX; + m_extentMaxX = TQ_INT32_MIN; + m_extentMaxY = TQ_INT32_MIN; +} + +KisTiledDataManager::KisTiledDataManager(const KisTiledDataManager & dm) + : KShared() +{ + m_pixelSize = dm.m_pixelSize; + + m_defPixel = new TQ_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 TQ_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]; + TQ_INT32 x,y,w,h; + + TQIODevice *stream = store->device(); + if (stream == 0) return false; + //Q_ASSERT(stream != 0); + + stream->readLine(str, 79); + + sscanf(str,"%u",&m_numTiles); + + for(TQ_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. + TQ_INT32 row = yToRow(y); + TQ_INT32 col = xToCol(x); + TQ_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(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_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; + } +} + +TQRect KisTiledDataManager::extent() const +{ + TQ_INT32 x; + TQ_INT32 y; + TQ_INT32 w; + TQ_INT32 h; + + extent(x, y, w, h); + + return TQRect(x, y, w, h); +} + +void KisTiledDataManager::setExtent(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h) +{ + TQRect newRect = TQRect(x, y, w, h).normalize(); + //printRect("newRect", newRect); + TQRect oldRect = TQRect(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.tqcontains(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) + { + TQRect tileRect = TQRect(tile->getCol() * KisTile::WIDTH, tile->getRow() * KisTile::HEIGHT, KisTile::WIDTH, KisTile::HEIGHT); + //printRect("tileRect", tileRect); + + if (newRect.tqcontains(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 + TQRect 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.tqcontains(x,y)) { + TQ_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 = TQ_INT32_MAX; + m_extentMinY = TQ_INT32_MAX; + m_extentMaxX = TQ_INT32_MIN; + m_extentMaxY = TQ_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(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, TQ_UINT8 clearValue) +{ + if (w < 1 || h < 1) { + return; + } + + TQ_INT32 firstColumn = xToCol(x); + TQ_INT32 lastColumn = xToCol(x + w - 1); + + TQ_INT32 firstRow = yToRow(y); + TQ_INT32 lastRow = yToRow(y + h - 1); + + TQRect clearRect(x, y, w, h); + + const TQ_UINT32 rowStride = KisTile::WIDTH * m_pixelSize; + + for (TQ_INT32 row = firstRow; row <= lastRow; ++row) { + for (TQ_INT32 column = firstColumn; column <= lastColumn; ++column) { + + KisTile *tile = getTile(column, row, true); + TQRect tileRect = tile->extent(); + + TQRect clearTileRect = clearRect & tileRect; + + tile->addReader(); + if (clearTileRect == tileRect) { + // Clear whole tile + memset(tile->data(), clearValue, KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize); + } else { + + TQ_UINT32 rowsRemaining = clearTileRect.height(); + TQ_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(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const TQ_UINT8 *clearPixel) +{ + Q_ASSERT(clearPixel != 0); + + if (clearPixel == 0 || w < 1 || h < 1) { + return; + } + + bool pixelBytesAreTheSame = true; + + for (TQ_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 { + + TQ_INT32 firstColumn = xToCol(x); + TQ_INT32 lastColumn = xToCol(x + w - 1); + + TQ_INT32 firstRow = yToRow(y); + TQ_INT32 lastRow = yToRow(y + h - 1); + + TQRect clearRect(x, y, w, h); + + const TQ_UINT32 rowStride = KisTile::WIDTH * m_pixelSize; + + TQ_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 TQ_UINT8[KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize]; + + TQ_UINT8 *dst = clearPixelData; + TQ_UINT32 pixelsRemaining = KisTile::WIDTH; + + // Generate one row + while (pixelsRemaining > 0) { + memcpy(dst, clearPixel, m_pixelSize); + dst += m_pixelSize; + --pixelsRemaining; + } + + TQ_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 + TQ_UINT32 maxRunLength = TQMIN(w, KisTile::WIDTH); + + clearPixelData = new TQ_UINT8[maxRunLength * m_pixelSize]; + + TQ_UINT8 *dst = clearPixelData; + TQ_UINT32 pixelsRemaining = maxRunLength; + + while (pixelsRemaining > 0) { + memcpy(dst, clearPixel, m_pixelSize); + dst += m_pixelSize; + --pixelsRemaining; + } + } + + for (TQ_INT32 row = firstRow; row <= lastRow; ++row) { + for (TQ_INT32 column = firstColumn; column <= lastColumn; ++column) { + + KisTile *tile = getTile(column, row, true); + TQRect tileRect = tile->extent(); + + TQRect clearTileRect = clearRect & tileRect; + + if (clearTileRect == tileRect) { + + // Clear whole tile + tile->addReader(); + memcpy(tile->data(), clearPixelData, KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize); + tile->removeReader(); + } else { + + TQ_UINT32 rowsRemaining = clearTileRect.height(); + tile->addReader(); + TQ_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 = TQ_INT32_MAX; + m_extentMinY = TQ_INT32_MAX; + m_extentMaxX = TQ_INT32_MIN; + m_extentMaxY = TQ_INT32_MIN; +} + +void KisTiledDataManager::paste(KisDataManagerSP data, TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 dx, TQ_INT32 dy, + TQ_INT32 w, TQ_INT32 h) +{ + //CBR_MISSING + sx=sy=dx=dy=w=h;data=0; +} + + +TQ_UINT32 KisTiledDataManager::calcTileHash(TQ_INT32 col, TQ_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) + { + TQ_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(TQ_INT32 col, TQ_INT32 row, TQ_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(TQ_INT32 col, TQ_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(TQ_INT32 col, TQ_INT32 row, bool writeAccess) +{ + TQ_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(TQ_INT32 col, TQ_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()); + + TQ_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; +} + +TQ_UINT8* KisTiledDataManager::pixelPtr(TQ_INT32 x, TQ_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 TQUITE VOLATILE + return pixelPtrSafe(x, y, writable) -> data(); +} + +KisTileDataWrapperSP KisTiledDataManager::pixelPtrSafe(TQ_INT32 x, TQ_INT32 y, bool writable) { + TQ_INT32 row = yToRow(y); + TQ_INT32 col = xToCol(x); + + // calc limits within the tile + TQ_INT32 yInTile = y - row * KisTile::HEIGHT; + TQ_INT32 xInTile = x - col * KisTile::WIDTH; + TQ_INT32 offset = m_pixelSize * (yInTile * KisTile::WIDTH + xInTile); + + KisTile *tile = getTile(col, row, writable); + + return new KisTileDataWrapper(tile, offset); +} + +const TQ_UINT8* KisTiledDataManager::pixel(TQ_INT32 x, TQ_INT32 y) +{ + return pixelPtr(x, y, false); +} + +TQ_UINT8* KisTiledDataManager::writablePixel(TQ_INT32 x, TQ_INT32 y) +{ + return pixelPtr(x, y, true); +} + +void KisTiledDataManager::setPixel(TQ_INT32 x, TQ_INT32 y, const TQ_UINT8 * data) +{ + TQ_UINT8 *pixel = pixelPtr(x, y, true); + memcpy(pixel, data, m_pixelSize); +} + + +void KisTiledDataManager::readBytes(TQ_UINT8 * data, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_INT32 h) +{ + if (data == 0) return; + //Q_ASSERT(data != 0); + if (w < 0) + w = 0; + + if (h < 0) + h = 0; + + TQ_INT32 dstY = 0; + TQ_INT32 srcY = y; + TQ_INT32 rowsRemaining = h; + + while (rowsRemaining > 0) { + + TQ_INT32 dstX = 0; + TQ_INT32 srcX = x; + TQ_INT32 columnsRemaining = w; + TQ_INT32 numContiguousSrcRows = numContiguousRows(srcY, srcX, srcX + w - 1); + + TQ_INT32 rows = TQMIN(numContiguousSrcRows, rowsRemaining); + + while (columnsRemaining > 0) { + + TQ_INT32 numContiguousSrcColumns = numContiguousColumns(srcX, srcY, srcY + rows - 1); + + TQ_INT32 columns = TQMIN(numContiguousSrcColumns, columnsRemaining); + + KisTileDataWrapperSP tileData = pixelPtrSafe(srcX, srcY, false); + const TQ_UINT8 *srcData = tileData -> data(); + TQ_INT32 srcRowStride = rowStride(srcX, srcY); + + TQ_UINT8 *dstData = data + ((dstX + (dstY * w)) * m_pixelSize); + TQ_INT32 dstRowStride = w * m_pixelSize; + + for (TQ_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 TQ_UINT8 * bytes, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_INT32 h) +{ + if (bytes == 0) return; + //Q_ASSERT(bytes != 0); + + // XXX: Is this correct? + if (w < 0) + w = 0; + + if (h < 0) + h = 0; + + TQ_INT32 srcY = 0; + TQ_INT32 dstY = y; + TQ_INT32 rowsRemaining = h; + + while (rowsRemaining > 0) { + + TQ_INT32 srcX = 0; + TQ_INT32 dstX = x; + TQ_INT32 columnsRemaining = w; + TQ_INT32 numContiguousdstRows = numContiguousRows(dstY, dstX, dstX + w - 1); + + TQ_INT32 rows = TQMIN(numContiguousdstRows, rowsRemaining); + + while (columnsRemaining > 0) { + + TQ_INT32 numContiguousdstColumns = numContiguousColumns(dstX, dstY, dstY + rows - 1); + + TQ_INT32 columns = TQMIN(numContiguousdstColumns, columnsRemaining); + + //TQ_UINT8 *dstData = writablePixel(dstX, dstY); + KisTileDataWrapperSP tileData = pixelPtrSafe(dstX, dstY, true); + TQ_UINT8 *dstData = tileData->data(); + TQ_INT32 dstRowStride = rowStride(dstX, dstY); + + const TQ_UINT8 *srcData = bytes + ((srcX + (srcY * w)) * m_pixelSize); + TQ_INT32 srcRowStride = w * m_pixelSize; + + for (TQ_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; + } +} + +TQ_INT32 KisTiledDataManager::numContiguousColumns(TQ_INT32 x, TQ_INT32 minY, TQ_INT32 maxY) +{ + TQ_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; +} + +TQ_INT32 KisTiledDataManager::numContiguousRows(TQ_INT32 y, TQ_INT32 minX, TQ_INT32 maxX) +{ + TQ_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; +} + +TQ_INT32 KisTiledDataManager::rowStride(TQ_INT32 x, TQ_INT32 y) +{ + Q_UNUSED(x); + Q_UNUSED(y); + + return KisTile::WIDTH * m_pixelSize; +} + +TQ_INT32 KisTiledDataManager::numTiles(void) const +{ + return m_numTiles; +} + +KisTileDataWrapper::KisTileDataWrapper(KisTile* tile, TQ_INT32 offset) + : m_tile(tile), m_offset(offset) +{ + m_tile->addReader(); +} + +KisTileDataWrapper::~KisTileDataWrapper() +{ + m_tile->removeReader(); +} diff --git a/chalk/core/tiles/kis_tileddatamanager.h b/chalk/core/tiles/kis_tileddatamanager.h new file mode 100644 index 00000000..20d78085 --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqvaluevector.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; + TQ_INT32 m_offset; +public: + KisTileDataWrapper(KisTile* tile, TQ_INT32 offset); + virtual ~KisTileDataWrapper(); + TQ_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 TQ_UINT8's a single pixel takes. + */ + +class KisTiledDataManager : public KShared { + +protected: + KisTiledDataManager(TQ_UINT32 pixelSize, const TQ_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 TQ_UINT8 *defPixel); + const TQ_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: + + TQ_UINT32 pixelSize(); + + void extent(TQ_INT32 &x, TQ_INT32 &y, TQ_INT32 &w, TQ_INT32 &h) const; + TQRect extent() const; + + void setExtent(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h); + +protected: + + void clear(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, TQ_UINT8 clearValue); + void clear(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const TQ_UINT8 *clearPixel); + void clear(); + + +protected: + + void paste(KisDataManagerSP data, TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 dx, TQ_INT32 dy, + TQ_INT32 w, TQ_INT32 h); + + +protected: + + + /** + * Get a read-only pointer to pixel (x, y). + */ + const TQ_UINT8* pixel(TQ_INT32 x, TQ_INT32 y); + + /** + * Get a read-write pointer to pixel (x, y). + */ + TQ_UINT8* writablePixel(TQ_INT32 x, TQ_INT32 y); + + /** + * write the specified data to x, y. There is no checking on pixelSize! + */ + void setPixel(TQ_INT32 x, TQ_INT32 y, const TQ_UINT8 * data); + + + /** + * Copy the bytes in the specified rect to a vector. The caller is responsible + * for managing the vector. + */ + void readBytes(TQ_UINT8 * bytes, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_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 TQ_UINT8 * bytes, + TQ_INT32 x, TQ_INT32 y, + TQ_INT32 w, TQ_INT32 h); + + /// Get the number of contiguous columns starting at x, valid for all values + /// of y between minY and maxY. + TQ_INT32 numContiguousColumns(TQ_INT32 x, TQ_INT32 minY, TQ_INT32 maxY); + + /// Get the number of contiguous rows starting at y, valid for all values + /// of x between minX and maxX. + TQ_INT32 numContiguousRows(TQ_INT32 y, TQ_INT32 minX, TQ_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). + TQ_INT32 rowStride(TQ_INT32 x, TQ_INT32 y); + + // For debugging use + TQ_INT32 numTiles() const; + +private: + + TQ_UINT32 m_pixelSize; + TQ_UINT32 m_numTiles; + KisTile *m_defaultTile; + KisTile **m_hashTable; + KisMementoSP m_currentMemento; + TQ_INT32 m_extentMinX; + TQ_INT32 m_extentMinY; + TQ_INT32 m_extentMaxX; + TQ_INT32 m_extentMaxY; + TQ_UINT8 *m_defPixel; + +private: + + void ensureTileMementoed(TQ_INT32 col, TQ_INT32 row, TQ_UINT32 tileHash, const KisTile *refTile); + KisTile *getOldTile(TQ_INT32 col, TQ_INT32 row, KisTile *def); + KisTile *getTile(TQ_INT32 col, TQ_INT32 row, bool writeAccess); + TQ_UINT32 calcTileHash(TQ_INT32 col, TQ_INT32 row); + void updateExtent(TQ_INT32 col, TQ_INT32 row); + void recalculateExtent(); + void deleteTiles(const KisMemento::DeletedTile *deletedTileList); + TQ_INT32 xToCol(TQ_INT32 x) const; + TQ_INT32 yToRow(TQ_INT32 y) const; + void getContiguousColumnsAndRows(TQ_INT32 x, TQ_INT32 y, TQ_INT32 *columns, TQ_INT32 *rows); + TQ_UINT8* pixelPtr(TQ_INT32 x, TQ_INT32 y, bool writable); + KisTileDataWrapperSP pixelPtrSafe(TQ_INT32 x, TQ_INT32 y, bool writable); +}; + + +inline TQ_UINT32 KisTiledDataManager::pixelSize() +{ + return m_pixelSize; +} + +inline TQ_INT32 KisTiledDataManager::xToCol(TQ_INT32 x) const +{ + if (x >= 0) { + return x / KisTile::WIDTH; + } else { + return -(((-x - 1) / KisTile::WIDTH) + 1); + } +} + +inline TQ_INT32 KisTiledDataManager::yToRow(TQ_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/chalk/core/tiles/kis_tiledhlineiterator.cc b/chalk/core/tiles/kis_tiledhlineiterator.cc new file mode 100644 index 00000000..cf023c1e --- /dev/null +++ b/chalk/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, TQ_INT32 x, TQ_INT32 y, TQ_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; + } +} + +TQ_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/chalk/core/tiles/kis_tilediterator.cc b/chalk/core/tiles/kis_tilediterator.cc new file mode 100644 index 00000000..d6205f5a --- /dev/null +++ b/chalk/core/tiles/kis_tilediterator.cc @@ -0,0 +1,131 @@ +/* + * This file is part of the Chalk + * + * 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; +} + +TQ_UINT8 * KisTiledIterator::rawData() const +{ + return m_data + m_offset; +} + + +const TQ_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(TQ_INT32 col, TQ_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/chalk/core/tiles/kis_tilediterator.h b/chalk/core/tiles/kis_tilediterator.h new file mode 100644 index 00000000..958876cd --- /dev/null +++ b/chalk/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 <tqglobal.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; + TQ_INT32 m_pixelSize; // bytes per pixel + TQ_INT32 m_x; // current x position + TQ_INT32 m_y; // cirrent y position + TQ_INT32 m_row; // row in tilemgr + TQ_INT32 m_col; // col in tilemgr + TQ_UINT8 *m_data; + TQ_UINT8 *m_oldData; + TQ_INT32 m_offset; + KisTile *m_tile; + KisTile* m_oldTile; + bool m_writable; + +protected: + inline TQ_UINT32 xToCol(TQ_UINT32 x) const { if (m_ktm) return m_ktm->xToCol(x); else return 0; }; + inline TQ_UINT32 yToRow(TQ_UINT32 y) const { if (m_ktm) return m_ktm->yToRow(y); else return 0; }; + void fetchTileData(TQ_INT32 col, TQ_INT32 row); + +public: + KisTiledIterator( KisTiledDataManager *ktm); + KisTiledIterator(const KisTiledIterator&); + KisTiledIterator& operator=(const KisTiledIterator&); + ~KisTiledIterator(); + +public: + // current x position + TQ_INT32 x() const { return m_x; }; + + // cirrent y position + TQ_INT32 y() const { return m_y; }; + + /// Returns a pointer to the pixel data. Do NOT interpret the data - leave that to a colorstrategy + TQ_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment tof he last memento creation. + const TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, bool writable); + KisTiledRectIterator(const KisTiledRectIterator&); + KisTiledRectIterator& operator=(const KisTiledRectIterator&); + ~KisTiledRectIterator(); + +public: + TQ_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: + TQ_INT32 m_left; + TQ_INT32 m_top; + TQ_INT32 m_w; + TQ_INT32 m_h; + TQ_INT32 m_topRow; + TQ_INT32 m_bottomRow; + TQ_INT32 m_leftCol; + TQ_INT32 m_rightCol; + TQ_INT32 m_xInTile; + TQ_INT32 m_yInTile; + TQ_INT32 m_leftInTile; + TQ_INT32 m_rightInTile; + TQ_INT32 m_topInTile; + TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_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 + TQ_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: + TQ_INT32 m_right; + TQ_INT32 m_left; + TQ_INT32 m_leftCol; + TQ_INT32 m_rightCol; + TQ_INT32 m_xInTile; + TQ_INT32 m_yInTile; + TQ_INT32 m_leftInTile; + TQ_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, TQ_INT32 x, TQ_INT32 y, TQ_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: + TQ_INT32 m_top; + TQ_INT32 m_bottom; + TQ_INT32 m_topRow; + TQ_INT32 m_bottomRow; + TQ_INT32 m_xInTile; + TQ_INT32 m_yInTile; + TQ_INT32 m_topInTile; + TQ_INT32 m_bottomInTile; + +private: + void nextTile(); +}; + +#endif // KIS_TILED_ITERATOR_H_ diff --git a/chalk/core/tiles/kis_tiledrectiterator.cc b/chalk/core/tiles/kis_tiledrectiterator.cc new file mode 100644 index 00000000..8f0f7ed1 --- /dev/null +++ b/chalk/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, TQ_INT32 nleft, + TQ_INT32 ntop, TQ_INT32 nw, TQ_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( ) +{ +} + +TQ_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/chalk/core/tiles/kis_tiledvlineiterator.cc b/chalk/core/tiles/kis_tiledvlineiterator.cc new file mode 100644 index 00000000..0fe8514f --- /dev/null +++ b/chalk/core/tiles/kis_tiledvlineiterator.cc @@ -0,0 +1,154 @@ +/* + * This file is part of the Chalk + * + * 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, TQ_INT32 x, TQ_INT32 y, TQ_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/chalk/core/tiles/kis_tilemanager.cc b/chalk/core/tiles/kis_tilemanager.cc new file mode 100644 index 00000000..dc9811e9 --- /dev/null +++ b/chalk/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 <tqmutex.h> +#include <tqthread.h> +#include <tqfile.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 TQ_UINT8*[4]; + m_poolPixelSizes = new TQ_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 TQMutex(true); + m_swapMutex = new TQMutex(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.tqcontains(tile)) { + m_swapMutex->unlock(); + return; + } + // Q_ASSERT(m_tileMap.tqcontains(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 (!chalkMmap(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); + TQFile* file = info->file->file(); + if(!file) { + kdWarning() << "Opening the file as TQFile failed" << endl; + m_swapForbidden = true; + m_swapMutex->unlock(); + return; + } + + int fd = file->handle(); + TQ_UINT8* data = 0; + if (!chalkMmap(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 + + TQ_UINT32 count = TQMIN(m_swappableList.size(), m_swappiness); + + for (TQ_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; +} + +TQ_UINT8* KisTileManager::requestTileData(TQ_INT32 pixelSize) +{ + m_swapMutex->lock(); + + TQ_UINT8* data = findTileFor(pixelSize); + if ( data ) { + m_swapMutex->unlock(); + return data; + } + m_swapMutex->unlock(); + return new TQ_UINT8[m_tileSize * pixelSize]; +} + +void KisTileManager::dontNeedTileData(TQ_UINT8* data, TQ_INT32 pixelSize) +{ + m_poolMutex->lock(); + if (isPoolTile(data, pixelSize)) { + reclaimTileToPool(data, pixelSize); + } else + delete[] data; + m_poolMutex->unlock(); +} + +TQ_UINT8* KisTileManager::findTileFor(TQ_INT32 pixelSize) +{ + m_poolMutex->lock(); + + for (int i = 0; i < 4; i++) { + if (m_poolPixelSizes[i] == pixelSize) { + if (!m_poolFreeList[i].isEmpty()) { + TQ_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 TQ_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(TQ_UINT8* data, TQ_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(TQ_UINT8* data, TQ_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::chalkMmap(TQ_UINT8*& result, void *start, size_t length, + int prot, int flags, int fd, off_t offset) { + result = (TQ_UINT8*) mmap(start, length, prot, flags, fd, offset); + + // Same here for warning and GUI + if (result == (TQ_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 Chalk, " + << "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/chalk/core/tiles/kis_tilemanager.h b/chalk/core/tiles/kis_tilemanager.h new file mode 100644 index 00000000..d6886abe --- /dev/null +++ b/chalk/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 <tqglobal.h> +#include <tqmap.h> +#include <tqvaluelist.h> +#include <tqmutex.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 + TQ_UINT8* requestTileData(TQ_INT32 pixelSize); + void dontNeedTileData(TQ_UINT8* data, TQ_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; + TQValueList<TileInfo*>::iterator node; + bool inMem; bool onFile; bool mmapped; bool validNode; }; + typedef struct { KTempFile* file; off_t filePos; int size; } FreeInfo; + typedef TQMap<const KisTile*, TileInfo*> TileMap; + typedef TQValueList<TileInfo*> TileList; + typedef TQValueList<FreeInfo*> FreeList; + typedef TQValueVector<FreeList> FreeListList; + typedef TQValueList<TQ_UINT8*> PoolFreeList; + typedef TQValueList<TempFile> FileList; + + + TileMap m_tileMap; + TileList m_swappableList; + FreeListList m_freeLists; + FileList m_files; + TQ_INT32 m_maxInMem; + TQ_INT32 m_currentInMem; + TQ_UINT32 m_swappiness; + TQ_INT32 m_tileSize; // size of a tile if it used 1 byte per pixel + unsigned long m_bytesInMem; + unsigned long m_bytesTotal; + + TQ_UINT8 **m_pools; + TQ_INT32 *m_poolPixelSizes; + TQ_INT32 m_tilesPerPool; + PoolFreeList *m_poolFreeList; + TQMutex * m_poolMutex; + TQMutex * 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 aptqparently 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(); + TQ_UINT8* findTileFor(TQ_INT32 pixelSize); + bool isPoolTile(TQ_UINT8* data, TQ_INT32 pixelSize); + void reclaimTileToPool(TQ_UINT8* data, TQ_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 chalkMmap(TQ_UINT8*& result, void *start, size_t length, + int prot, int flags, int fd, off_t offset); +}; + +#endif // KIS_TILEMANAGER_H_ diff --git a/chalk/core/tiles/tests/Makefile.am b/chalk/core/tiles/tests/Makefile.am new file mode 100644 index 00000000..c546d7f5 --- /dev/null +++ b/chalk/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 ../../../libchalkcommon.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/chalk/core/tiles/tests/kis_tiled_data_tester.cpp b/chalk/core/tiles/tests/kis_tiled_data_tester.cpp new file mode 100644 index 00000000..15d3e50b --- /dev/null +++ b/chalk/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 TQ_UINT8 defaultPixel[TEST_PIXEL_SIZE] = {0, 0, 0, OPACITY_TRANSPARENT}; + +void KisTiledDataTester::allTests() +{ + KisDataManager *dm = new KisDataManager(TEST_PIXEL_SIZE, defaultPixel); + + TQ_INT32 extentX; + TQ_INT32 extentY; + TQ_INT32 extentWidth; + TQ_INT32 extentHeight; + + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentWidth, 0); + CHECK(extentHeight, 0); + + const TQ_UINT8 *readOnlyPixel = dm->pixel(KisTile::WIDTH/2, KisTile::HEIGHT/2); + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentWidth, 0); + CHECK(extentHeight, 0); + + TQ_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/chalk/core/tiles/tests/kis_tiled_data_tester.h b/chalk/core/tiles/tests/kis_tiled_data_tester.h new file mode 100644 index 00000000..8a569d23 --- /dev/null +++ b/chalk/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 + |