chalk

kis_imagepipe_brush.cpp
1/*
2 * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
3 * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#ifdef HAVE_SYS_TYPES_H
25#include <sys/types.h>
26#endif
27
28#include <math.h>
29
30#include <netinet/in.h>
31#include <limits.h>
32#include <stdlib.h>
33
34#include <tqimage.h>
35#include <tqpoint.h>
36#include <tqvaluevector.h>
37#include <tqfile.h>
38#include <tqregexp.h>
39#include <tqstringlist.h>
40#include <tqtextstream.h>
41
42#include <kdebug.h>
43#include <tdelocale.h>
44#include <tdeapplication.h>
45
46#include "kis_global.h"
47#include "kis_paint_device.h"
48#include "kis_imagepipe_brush.h"
49#include "kis_brush.h"
50#include "kis_alpha_mask.h"
51#include "kis_layer.h"
52#include "kis_meta_registry.h"
53#include "kis_colorspace_factory_registry.h"
54
55
57{
58 needsMovement = false;
59 TQRegExp basicSplitter(" ", true);
60 TQRegExp parasiteSplitter(":", true);
61 TQStringList parasites = TQStringList::split(basicSplitter, source);
62 for (uint i = 0; i < parasites.count(); i++) {
63 TQStringList splitted = TQStringList::split(parasiteSplitter, *parasites.at(i));
64 if (splitted.count() != 2) {
65 kdWarning(41001) << "Wrong count for this parasite key/value:" << *parasites.at(i) << endl;
66 continue;
67 }
68 TQString index = *splitted.at(0);
69 if (index == "dim") {
70 dim = (*splitted.at(1)).toInt();
71 if (dim < 1 || dim > MaxDim) {
72 dim = 1;
73 }
74 } else if (index.startsWith("sel")) {
75 int selIndex = index.mid(strlen("sel")).toInt();
76 if (selIndex >= 0 && selIndex < dim) {
77 TQString selectionMode = *splitted.at(1);
78 if (selectionMode == "incremental")
79 selection[selIndex] = Incremental;
80 else if (selectionMode == "angular") {
81 selection[selIndex] = Angular;
82 needsMovement = true;
83 } else if (selectionMode == "random")
84 selection[selIndex] = Random;
85 else if (selectionMode == "pressure")
86 selection[selIndex] = Pressure;
87 else if (selectionMode == "xtilt")
88 selection[selIndex] = TiltX;
89 else if (selectionMode == "ytilt")
90 selection[selIndex] = TiltY;
91 else
92 selection[selIndex] = Constant;
93 } else {
94 kdWarning(41001)<< "Sel: wrong index: " << selIndex << "(dim = " << dim << ")" << endl;
95 }
96 } else if (index.startsWith("rank")) {
97 int rankIndex = index.mid(strlen("rank")).toInt();
98 if (rankIndex < 0 || rankIndex > dim) {
99 kdWarning(41001) << "Rankindex out of range: " << rankIndex << endl;
100 continue;
101 }
102 rank[rankIndex] = (*splitted.at(1)).toInt();
103 } else if (index == "ncells") {
104 ncells = (*splitted.at(1)).toInt();
105 if (ncells < 1 ) {
106 kdWarning(41001) << "ncells out of range: " << ncells << endl;
107 ncells = 1;
108 }
109 }
110 }
111
112 for (int i = 0; i < dim; i++) {
113 index[i] = 0;
114 }
115
117}
118
120 // I assume ncells is correct. If it isn't, complain to the parasite header.
121 brushesCount[0] = ncells / rank[0];
122 for (int i = 1; i < dim; i++) {
123 brushesCount[i] = brushesCount[i-1] / rank[i];
124 }
125}
126
127bool KisPipeBrushParasite::saveToDevice(TQIODevice* dev) const {
128 // write out something like
129 // <count> ncells:<count> dim:<dim> rank0:<rank0> sel0:<sel0> <...>
130
131 TQTextStream stream(dev);
133 stream << ncells << " ncells:" << ncells << " dim:" << dim;
134
135 for (int i = 0; i < dim; i++) {
136 stream << " rank" << i << ":" << rank[i] << " sel" << i << ":";
137 switch (selection[i]) {
138 case Constant: stream << "constant"; break;
139 case Incremental: stream << "incremental"; break;
140 case Angular: stream << "angular"; break;
141 case Velocity: stream << "velocity"; break;
142 case Random: stream << "random"; break;
143 case Pressure: stream << "pressure"; break;
144 case TiltX: stream << "xtilt"; break;
145 case TiltY: stream << "ytilt"; break;
146 }
147 }
148
149 return true;
150}
151
152KisImagePipeBrush::KisImagePipeBrush(const TQString& filename) : super(filename)
153{
154 m_brushType = INVALID;
155 m_numOfBrushes = 0;
156 m_currentBrush = 0;
157}
158
159KisImagePipeBrush::KisImagePipeBrush(const TQString& name, int w, int h,
160 TQValueVector< TQValueVector<KisPaintDevice*> > devices,
161 TQValueVector<KisPipeBrushParasite::SelectionMode> modes)
162 : super("")
163{
164 Q_ASSERT(devices.count() == modes.count());
165 Q_ASSERT(devices.count() > 0);
166 Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim!
167
168 setName(name);
169
170 m_parasite.dim = devices.count();
171 // XXX Change for multidim! :
172 m_parasite.ncells = devices.at(0).count();
173 m_parasite.rank[0] = m_parasite.ncells;
174 m_parasite.selection[0] = modes.at(0);
175 // XXX needsmovement!
176
177 m_parasite.setBrushesCount();
178
179 for (uint i = 0; i < devices.at(0).count(); i++) {
180 m_brushes.append(new KisBrush(devices.at(0).at(i), 0, 0, w, h));
181 }
182
183 setImage(m_brushes.at(0)->img());
184
185 m_brushType = PIPE_IMAGE;
186}
187
188KisImagePipeBrush::~KisImagePipeBrush()
189{
190 m_brushes.setAutoDelete(true);
191 m_brushes.clear();
192}
193
195{
196 TQFile file(filename());
197 file.open(IO_ReadOnly);
198 m_data = file.readAll();
199 file.close();
200 return init();
201}
202
203bool KisImagePipeBrush::init()
204{
205 // XXX: this doesn't correctly load the image pipe brushes yet.
206
207 // XXX: This stuff is in utf-8, too.
208 // The first line contains the name -- this means we look until we arrive at the first newline
209 TQValueVector<char> line1;
210
211 TQ_UINT32 i = 0;
212
213 while (m_data[i] != '\n' && i < m_data.size()) {
214 line1.append(m_data[i]);
215 i++;
216 }
217 setName(i18n(TQString::fromUtf8(&line1[0], i).ascii()));
218
219 i++; // Skip past the first newline
220
221 // The second line contains the number of brushes, separated by a space from the parasite
222
223 // XXX: This stuff is in utf-8, too.
224 TQValueVector<char> line2;
225 while (m_data[i] != '\n' && i < m_data.size()) {
226 line2.append(m_data[i]);
227 i++;
228 }
229
230 TQString paramline = TQString::fromUtf8((&line2[0]), line2.size());
231 TQ_UINT32 m_numOfBrushes = paramline.left(paramline.find(' ')).toUInt();
232 m_parasite = paramline.mid(paramline.find(' ') + 1);
233 i++; // Skip past the second newline
234
235 TQ_UINT32 numOfBrushes = 0;
236 while (numOfBrushes < m_numOfBrushes && i < m_data.size()){
237 KisBrush * brush = new KisBrush(name() + "_" + numOfBrushes,
238 m_data,
239 i);
240 TQ_CHECK_PTR(brush);
241
242 m_brushes.append(brush);
243
244 numOfBrushes++;
245 }
246
247 if (!m_brushes.isEmpty()) {
248 setValid(true);
249 if (m_brushes.at( 0 )->brushType() == MASK) {
250 m_brushType = PIPE_MASK;
251 }
252 else {
253 m_brushType = PIPE_IMAGE;
254 }
255 setSpacing(m_brushes.at(m_brushes.count() - 1)->spacing());
256 setWidth(m_brushes.at(0)->width());
257 setHeight(m_brushes.at(0)->height());
258 }
259
260 m_data.resize(0);
261 return true;
262}
263
265{
266 TQFile file(filename());
267 file.open(IO_WriteOnly | IO_Truncate);
268 bool ok = saveToDevice(&file);
269 file.close();
270 return ok;
271}
272
273bool KisImagePipeBrush::saveToDevice(TQIODevice* dev) const
274{
275 TQCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8
276 char const* name = utf8Name.data();
277 int len = tqstrlen(name);
278
279 if (parasite().dim != 1) {
280 kdWarning(41001) << "Save to file for pipe brushes with dim != not yet supported!" << endl;
281 return false;
282 }
283
284 // Save this pipe brush: first the header, and then all individual brushes consecutively
285 // (this needs some care for when we have > 1 dimension), FIXME
286
287 // Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n
288
289 // The name\n
290 if (dev->writeBlock(name, len) == -1)
291 return false;
292
293 if (dev->putch('\n') == -1)
294 return false;
295
296 // Write the parasite (also writes number of brushes)
297 if (!m_parasite.saveToDevice(dev))
298 return false;
299
300 if (dev->putch('\n') == -1)
301 return false;
302
303 // <gbr brushes>
304 for (uint i = 0; i < m_brushes.count(); i++)
305 if (!m_brushes.at(i)->saveToDevice(dev))
306 return false;
307
308 return true;
309}
310
312{
313 if (m_brushes.isEmpty()) {
314 return 0;
315 }
316 else {
317 return m_brushes.at(0)->img();
318 }
319}
320
321KisAlphaMaskSP KisImagePipeBrush::mask(const KisPaintInformation& info, double subPixelX, double subPixelY) const
322{
323 if (m_brushes.isEmpty()) return 0;
324 selectNextBrush(info);
325 return m_brushes.at(m_currentBrush)->mask(info, subPixelX, subPixelY);
326}
327
328KisPaintDeviceSP KisImagePipeBrush::image(KisColorSpace * colorSpace, const KisPaintInformation& info, double subPixelX, double subPixelY) const
329{
330 if (m_brushes.isEmpty()) return 0;
331 selectNextBrush(info);
332 return m_brushes.at(m_currentBrush)->image(colorSpace, info, subPixelX, subPixelY);
333}
334
335void KisImagePipeBrush::setParasiteString(const TQString& parasite)
336{
337 m_parasiteString = parasite;
338 m_parasite = KisPipeBrushParasite(parasite);
339}
340
341
342enumBrushType KisImagePipeBrush::brushType() const
343{
344 if (m_brushType == PIPE_IMAGE && useColorAsMask()) {
345 return PIPE_MASK;
346 }
347 else {
348 return m_brushType;
349 }
350}
351
352bool KisImagePipeBrush::useColorAsMask() const
353{
354 if (m_brushes.count() > 0) {
355 return m_brushes.at(0)->useColorAsMask();
356 }
357 else {
358 return false;
359 }
360}
361
362void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask)
363{
364 for (uint i = 0; i < m_brushes.count(); i++) {
365 m_brushes.at(i)->setUseColorAsMask(useColorAsMask);
366 }
367}
368
369bool KisImagePipeBrush::hasColor() const
370{
371 if (m_brushes.count() > 0) {
372 return m_brushes.at(0)->hasColor();
373 }
374 else {
375 return false;
376 }
377}
378
379KisBoundary KisImagePipeBrush::boundary() {
380 Q_ASSERT(!m_brushes.isEmpty());
381 return m_brushes.at(0)->boundary();
382}
383
384void KisImagePipeBrush::selectNextBrush(const KisPaintInformation& info) const {
385 m_currentBrush = 0;
386 double angle;
387 for (int i = 0; i < m_parasite.dim; i++) {
388 int index = m_parasite.index[i];
389 switch (m_parasite.selection[i]) {
390 case KisPipeBrushParasite::Constant: break;
391 case KisPipeBrushParasite::Incremental:
392 index = (index + 1) % m_parasite.rank[i]; break;
393 case KisPipeBrushParasite::Random:
394 index = int(float(m_parasite.rank[i])*TDEApplication::random() / RAND_MAX); break;
395 case KisPipeBrushParasite::Pressure:
396 index = static_cast<int>(info.pressure * (m_parasite.rank[i] - 1) + 0.5); break;
397 case KisPipeBrushParasite::Angular:
398 // + M_PI_2 to be compatible with the gimp
399 angle = atan2(info.movement.y(), info.movement.x()) + M_PI_2;
400 // We need to be in the [0..2*Pi[ interval so that we can more nicely select it
401 if (angle < 0)
402 angle += 2.0 * M_PI;
403 else if (angle > 2.0 * M_PI)
404 angle -= 2.0 * M_PI;
405 index = static_cast<int>(angle / (2.0 * M_PI) * m_parasite.rank[i]);
406 break;
407 default:
408 kdWarning(41001) << "This parasite selectionMode has not been implemented. Reselecting"
409 << " to Incremental" << endl;
410 m_parasite.selection[i] = KisPipeBrushParasite::Incremental;
411 index = 0;
412 }
413 m_parasite.index[i] = index;
414 m_currentBrush += m_parasite.brushesCount[i] * index;
415 }
416}
417
418bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info) {
419 if (info.movement.isNull() && m_parasite.needsMovement)
420 return false;
421 return true;
422}
423
424void KisImagePipeBrush::makeMaskImage() {
425 for (uint i = 0; i < m_brushes.count(); i++)
426 m_brushes.at(i)->makeMaskImage();
427
428 setBrushType(PIPE_MASK);
429 setUseColorAsMask(false);
430}
431
432KisImagePipeBrush* KisImagePipeBrush::clone() const {
433 // The obvious way of cloning each brush in this one doesn't work for some reason...
434
435 // XXX Multidimensionals not supported yet, change together with the constructor...
436 TQValueVector< TQValueVector<KisPaintDevice*> > devices;
437 TQValueVector<KisPipeBrushParasite::SelectionMode> modes;
438
439 devices.push_back(TQValueVector<KisPaintDevice*>());
440 modes.push_back(m_parasite.selection[0]);
441
442 for (uint i = 0; i < m_brushes.count(); i++) {
443 KisPaintDevice* pd = new KisPaintDevice(
444 KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""), "clone pd" );
445 pd->convertFromTQImage(m_brushes.at(i)->img(), "");
446 devices.at(0).append(pd);
447 }
448
449 KisImagePipeBrush* c = new KisImagePipeBrush(name(), width(), height(), devices, modes);
450 // XXX clean up devices
451
452 return c;
453}
454
455#include "kis_imagepipe_brush.moc"
456
Generates an 'outline' for a paint device.
A colorspace is the definition of a certain color model in Chalk.
virtual void convertFromTQImage(const TQImage &img, const TQString &srcProfileName, TQ_INT32 offsetX=0, TQ_INT32 offsetY=0)
Fill this paint device with the data from img; starting at (offsetX, offsetY).
This class keeps information that can be used in the painting process, for example by brushes.
Definition kis_paintop.h:50
KisPipeBrushParasite()
Set some default values.
TQ_INT32 brushesCount[MaxDim]
The total count of brushes in each dimension (helper).
void setBrushesCount()
Initializes the brushesCount helper.
bool saveToDevice(TQIODevice *dev) const
Saves a GIMP-compatible representation of this parasite to the device.
bool needsMovement
If true, the brush won't be painted when there is no motion.
TQ_INT32 index[MaxDim]
The current index in each dimension, so that the selection modes know where to start.
virtual TQImage img()=0
Returns a TQImage representing this resource.
virtual bool save()=0
Save this resource asynchronously.
virtual bool load()=0
Load this resource.