diff options
Diffstat (limited to 'chalk/core/kis_imagepipe_brush.cc')
-rw-r--r-- | chalk/core/kis_imagepipe_brush.cc | 456 |
1 files changed, 456 insertions, 0 deletions
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" + |