1
/***************************************************************************
2
* Copyright (C) 2003 by Stephen Allewell *
3
* stephen@mirramar.fsnet.co.uk *
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
***************************************************************************/
11
#include <qdatastream.h>
13
#include "patterncanvas.h"
16
PatternCanvas::PatternCanvas()
19
m_pCanvas(new QPtrVector<Stitch::Queue>),
20
m_pKnot(new QPtrList<Knot>),
21
m_pBackstitch(new QPtrList<BackStitch>)
23
m_pCanvas->setAutoDelete(true);
24
m_pKnot->setAutoDelete(true);
25
m_pBackstitch->setAutoDelete(true);
28
PatternCanvas::~PatternCanvas()
35
void PatternCanvas::clear()
37
m_pBackstitch->clear();
44
bool PatternCanvas::isEmpty()
46
if (m_pBackstitch->isEmpty() && m_pKnot->isEmpty() && m_pCanvas->isEmpty())
52
void PatternCanvas::resize(int width,int height)
54
m_pCanvas->resize(width*height);
59
void PatternCanvas::extendTop(int rows)
65
int newSize = m_width*m_height;
67
QPoint snapOffset = QPoint(0,rows*2);
69
m_pCanvas->insert(--newSize, m_pCanvas->take(--size));
70
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
72
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
74
bs->start += snapOffset;
75
bs->end += snapOffset;
79
void PatternCanvas::extendLeft(int cols)
84
resize(m_width+cols, m_height);
85
int newSize = m_width*m_height;
86
QPoint snapOffset = QPoint(cols*2,0);
89
for (int c = w ; c ; c--)
90
m_pCanvas->insert(--newSize, m_pCanvas->take(--size));
93
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
95
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
97
bs->start += snapOffset;
98
bs->end += snapOffset;
102
void PatternCanvas::extendRight(int cols)
107
resize(m_width+cols, m_height);
108
int newSize = m_width*m_height;
111
for (int c = w ; c ; c--)
112
m_pCanvas->insert((--newSize)-cols, m_pCanvas->take(--size));
117
void PatternCanvas::extendBottom(int rows)
119
resize(m_width, m_height+rows);
122
QRect PatternCanvas::patternBoundingRect()
124
int canvasW = m_width;
125
int canvasH = m_height;
126
int canvasSize = canvasW*canvasH;
129
if (!m_pCanvas->isEmpty())
130
for (int i = 0 ; i < canvasSize ; i++)
134
if (m_pCanvas->at(i))
135
stitchesRect|=QRect(dx,dy,1,1);
138
if (!m_pKnot->isEmpty())
140
int x1 = canvasW*2+1;
141
int y1 = canvasH*2+1;
144
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
146
x1 = x1 <? k->pos.x();
147
y1 = y1 <? k->pos.y();
148
x2 = x2 >? k->pos.x();
149
y2 = y2 >? k->pos.y();
151
stitchesRect|=QRect(x1/2,y1/2,(x2-x1)/2,(y2-y1)/2);
154
if (!m_pBackstitch->isEmpty())
156
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
158
// create a normalised rectangle that bounds the backstitch
159
QPoint s = bs->start;
166
if (x1 == x2 || y1 == y2)
168
// special case as a QRect would show as zero m_width or m_height (ie invalid)
169
// so work out were this backstitch is in relation to the canvas
170
// and extend the bounding rectangle to suit.
171
// worst case would be if there was only 1 vertical or horizontal backstitch and nothing else, but this is unlikely
175
if (x1/2 < canvasW/2)
177
// its to the left of canvas
178
x2+=2; // backstitch coordinates
180
else if (x1/2 == canvasW/2)
182
// its in the middle of the canvas
188
// its to the right of canvas
195
if (y1/2 < canvasH/2)
197
// its above the canvas center
200
else if (y1/2 == canvasH/2)
202
// its in the middle of the canvas
208
// its below the canvas center
213
stitchesRect|=QRect(x1/2,y1/2,(x2-x1)/2,(y2-y1)/2);
219
void PatternCanvas::centerPattern()
221
int canvasW = m_width;
222
int canvasH = m_height;
223
int canvasSize = canvasW*canvasH;
225
QRect stitchesRect = patternBoundingRect();
226
// stitchesRect now bounds stitches and backstitches
227
// if it is still invalid then the pattern is empty
228
if (stitchesRect.isValid())
230
// center the rectangle in the pattern
231
int patternW = stitchesRect.width();
232
int patternH = stitchesRect.height();
233
int destinationX = (canvasW-patternW)/2;
234
int destinationY = (canvasH-patternH)/2; // destinationX,destinationY is the position of the top left of the destination rectangle
236
int destinationOrigin = destinationY*canvasW+destinationX; // index to QVector of top left of destination rectangle
237
int sourceOrigin = stitchesRect.y()*canvasW+stitchesRect.x(); // index to QVector of top left of source rectangle
238
QPoint offset((destinationX-stitchesRect.x())*2,(destinationY-stitchesRect.y())*2);
239
if (destinationOrigin != sourceOrigin)
241
if (destinationOrigin < sourceOrigin)
244
while (sourceOrigin < canvasSize)
245
m_pCanvas->insert(destinationOrigin++, m_pCanvas->take(sourceOrigin++));
250
int offset = destinationOrigin-sourceOrigin;
251
sourceOrigin = stitchesRect.bottom()*canvasW+stitchesRect.right();
252
destinationOrigin = sourceOrigin+offset;
253
while (sourceOrigin >= 0)
254
m_pCanvas->insert(destinationOrigin--, m_pCanvas->take(sourceOrigin--));
257
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
259
// offset the backstitches
260
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
266
// else no move is required
270
void PatternCanvas::cropCanvasToRect(QRect r)
276
int destinationIndex = 0;
277
for (int y = 0 ; y < rH ; y++)
278
for (int x = 0 ; x < rW ; x++)
279
m_pCanvas->insert(destinationIndex++, m_pCanvas->take((y+ry)*m_width+rx+x));
281
Knot* kn = m_pKnot->first();
284
kn->pos -= QPoint(rx*2,ry*2);
285
if (kn->pos.x() < 0 || kn->pos.y() < 0 || kn->pos.x() > rW*2 || kn->pos.y() > rH*2)
289
kn = m_pKnot->current();
291
BackStitch* bs = m_pBackstitch->first();
294
bs->start -= QPoint(rx*2,ry*2);
295
bs->end -= QPoint(rx*2,ry*2);
296
if (bs->start.x() < 0 || bs->start.y() < 0 || bs->end.x() < 0 || bs->end.y() < 0 || bs->start.x() > rW*2 || bs->start.y() > rH*2 || bs->end.x() > rW*2 || bs->end.y() > rH*2)
297
m_pBackstitch->remove();
299
m_pBackstitch->next();
300
bs = m_pBackstitch->current();
304
int PatternCanvas::patternWidth()
309
int PatternCanvas::patternHeight()
314
uint PatternCanvas::index(QPoint c)
316
return c.y()*m_width+c.x();
319
Stitch::Queue *PatternCanvas::stitchAt(QPoint cell)
321
return (m_pCanvas && validateCell(cell))?m_pCanvas->at(index(cell)):0;
324
bool PatternCanvas::validateCell(QPoint c)
326
if (c.x() < 0) return false;
327
if (c.y() < 0) return false;
328
if (m_width <= c.x()) return false;
329
if (m_height <= c.y()) return false;
333
bool PatternCanvas::validateSnap(QPoint s)
335
if (s.x() < 0) return false;
336
if (s.y() < 0) return false;
337
if (m_width*2+1 <= s.x()) return false;
338
if (m_height*2+1 <= s.y()) return false;
342
QPtrListIterator<BackStitch> *PatternCanvas::backstitches()
344
return new QPtrListIterator<BackStitch>(*m_pBackstitch);
347
QPtrListIterator<Knot> *PatternCanvas::knots()
349
return new QPtrListIterator<Knot>(*m_pKnot);
352
bool PatternCanvas::deleteStitch(QPoint c,Stitch::Type t,int f)
354
if (!validateCell(c))
357
if (t == Stitch::Delete && f == -1)
359
m_pCanvas->remove(id);
362
Stitch::Queue *q = m_pCanvas->at(id);
368
Stitch *s = q->dequeue();
369
if (!(t == Stitch::Delete ^ s->type == t) || !(f == -1 ^ s->floss == f))
375
m_pCanvas->remove(id);
381
bool PatternCanvas::addStitch(QPoint c, Stitch::Type t, int i)
383
if (!validateCell(c))
387
Stitch::Queue *pStitches = m_pCanvas->at(id); // get the pointer to any existing queue of stitches for this location
390
/** no stitch queue currently exists for this location */
391
pStitches = new Stitch::Queue();
392
m_pCanvas->insert(id, pStitches); // insert a new stitch queue
395
uint nStitches = pStitches->count(); // get the number of stitches in the queue (may be none)
396
pStitches->enqueue(new Stitch(t, i)); // add the new stitch to the end of the queue
398
/** iterate the queue of existing stitches for any that have been overwritten by the new stitch */
399
while (nStitches--) // while there are existing stitches
401
Stitch *pStitch = pStitches->dequeue(); // get the stitch at the head of the queue
402
Stitch::Type cst = pStitch->type; // and find its type
403
int cfi = pStitch->floss; // and colour
404
Stitch::Type interferenceMask = (Stitch::Type)(cst & t);
405
// interferenceMask now contains a mask of which bits are affected by new stitch
406
if (interferenceMask)
408
// Some parts of the current stitch are being overwritten
409
// but a check needs to be made for illegal values
410
Stitch::Type changeMask = (Stitch::Type)(cst ^ interferenceMask);
413
// changeMask contains what is left of the original stitch after being overwritten
414
// it may contain illegal values, so these are checked for
416
pStitches->enqueue(new Stitch(Stitch::TLQtr, cfi));
417
pStitches->enqueue(new Stitch(Stitch::TRQtr, cfi));
418
changeMask = Stitch::Delete;
421
pStitches->enqueue(new Stitch(Stitch::TLQtr, cfi));
422
pStitches->enqueue(new Stitch(Stitch::BLQtr, cfi));
423
changeMask = Stitch::Delete;
426
pStitches->enqueue(new Stitch(Stitch::TRQtr, cfi));
427
pStitches->enqueue(new Stitch(Stitch::BRQtr, cfi));
428
changeMask = Stitch::Delete;
431
pStitches->enqueue(new Stitch(Stitch::BLQtr, cfi));
432
pStitches->enqueue(new Stitch(Stitch::BRQtr, cfi));
433
changeMask = Stitch::Delete;
436
// other values are acceptable as is
441
// Check there is something left of the original stitch
442
pStitch->type = changeMask; // and change stitch type to the changeMask value
443
pStitches->enqueue(pStitch); // and then add it back to the queue
447
// if changeMask is NULL, it does not get requeued, effectively deleting it from the pattern
448
delete pStitch; // delete the Stitch as it is no longer required
453
pStitches->enqueue(pStitch);
459
int PatternCanvas::findColor(QPoint cell, Stitch::Type stitchType)
463
if (validateCell(cell))
465
uint id = index(cell);
466
Stitch::Queue *pStitches = m_pCanvas->at(id); // get the pointer to any existing queue of stitches for this location
469
uint nStitches = pStitches->count(); // get the number of stitches in the queue (may be none)
470
/** iterate the queue of existing stitches */
471
while (nStitches--) // while there are existing stitches
473
Stitch *pStitch = pStitches->dequeue(); // get the stitch at the head of the queue
474
Stitch::Type cst = pStitch->type; // and find its type
475
int cfi = pStitch->floss; // and colour
476
if (colorIndex == -1)
477
colorIndex = cfi; // set the colorIndex to the first color found
478
if (cst & stitchType)
479
colorIndex = cfi; // change the colorIndex if the current stitch type found
480
pStitches->enqueue(pStitch); // put the stitch back in the queue
487
void PatternCanvas::replaceColor(int original, int replacement)
489
int sq = m_pCanvas->size();
490
for (int i = 0 ; i < sq ; i++)
492
Stitch::Queue *q = m_pCanvas->at(i);
498
Stitch *s = q->dequeue();
500
// shouldn't really happen, but check for it anyway
501
if (s->floss == original)
502
s->floss = replacement;
507
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
508
if (bs->floss == original)
509
bs->floss = replacement;
510
for (Knot* k = m_pKnot->first() ; k ; k = m_pKnot->next())
511
if (k->floss == original)
512
k->floss = replacement;
515
QValueList<int> PatternCanvas::usedColors()
518
int sq = m_pCanvas->size();
519
for (int i = 0 ; i < sq ; i++)
521
Stitch::Queue *pStitches = m_pCanvas->at(i);
524
int c = pStitches->count();
527
Stitch *s = pStitches->dequeue();
529
{ // shouldn't really happen, but check for it anyway
530
if (!uc.contains(s->floss))
532
pStitches->enqueue(s);
537
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
538
if (!uc.contains(k->floss))
540
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
541
if (!uc.contains(bs->floss))
546
void PatternCanvas::addBackstitch(QPoint start, QPoint end, int i)
548
if (validateSnap(start) && validateSnap(end))
549
m_pBackstitch->append(new BackStitch(start, end, i));
552
void PatternCanvas::deleteBackstitch(BackStitch *bs)
554
m_pBackstitch->removeRef(bs);
557
void PatternCanvas::addFrenchKnot(QPoint snap, int i)
559
if (validateSnap(snap))
561
deleteFrenchKnot(snap,-1); // just in case there is one there already
562
m_pKnot->append(new Knot(snap,i));
566
void PatternCanvas::deleteFrenchKnot(QPoint p, int f)
568
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
569
if (k->pos == p && (f == -1 ^ f == k->floss))
576
void PatternCanvas::writeCanvas(QDataStream &s)
578
s << (Q_INT32)m_width;
579
s << (Q_INT32)m_height;
581
int sq = m_pCanvas->size();
582
for (int i = 0 ; i < sq ; i++)
584
Stitch::Queue *psq = m_pCanvas->at(i);
587
int stitches = psq->count(); // the number of stitches in the queue, may be 0
588
s << (Q_INT8)stitches;
591
Stitch *stitch = psq->dequeue();
592
s << (Q_INT8)(stitch->type);
593
s << (Q_INT16)(stitch->floss);
594
psq->enqueue(stitch);
600
sq = m_pKnot->count();
602
for (Knot *k = m_pKnot->first() ; k ; k = m_pKnot->next())
603
s << k->pos << (Q_INT16)(k->floss); // QPoint, Q_INT16
604
sq = m_pBackstitch->count();
606
for (BackStitch* bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
607
s << bs->start << bs->end << (Q_INT16)(bs->floss); // QPoint, QPoint, Q_INT16
610
bool PatternCanvas::readPCStitch5Canvas(QDataStream& s)
612
Stitch::Type stitchType[] = {Stitch::Delete,Stitch::Full,Stitch::TL3Qtr,Stitch::TR3Qtr,Stitch::BL3Qtr,Stitch::BR3Qtr,Stitch::TBHalf,Stitch::BTHalf,Stitch::FRKnot,Stitch::TLQtr,Stitch::TRQtr,Stitch::BLQtr,Stitch::BRQtr}; // conversion of PCStitch to KXStitch
616
Q_INT32 backstitches;
623
QIODevice::Offset fileSize = s.device()->size();
624
// KXStitch file format has the m_width and m_height available at this point
625
// PCStitch does not, so it has been set by KXStitchDoc::openDocument()
626
// and the canvas has been cleared before calling this function
629
cells = width*height; // total number of cells
630
for (int i = 0 ; i < cells ; i+= cellCount)
632
if (s.device()->at()+4 > fileSize) return false;
638
for (int c = 0 ; c < cellCount ; c++)
640
int xc = (i+c)/height;
641
int yc = (i+c)%height;
642
addStitch(QPoint(xc,yc),stitchType[type],color-1); // color-1 because PCStitch uses 1 based array
643
// KXStitch uses 0 based arrays
647
// check for additional stitches
648
if (s.device()->at()+4 > fileSize) return false;
649
s >> extras; // assuming a 32 bit value, time will tell
652
if (s.device()->at()+4 > fileSize) return false;
655
for (int dx = 0 ; dx < 4 ; dx++)
657
if (s.device()->at()+2 > fileSize) return false;
661
addStitch(QPoint(x-1,y-1),stitchType[type],color-1); // values -1 for same reason as above
665
if (s.device()->at()+4 > fileSize) return false;
669
if (s.device()->at()+5 > fileSize) return false;
673
addFrenchKnot(QPoint(x-1,y-1),color-1);
676
if (s.device()->at()+4 > fileSize) return false;
678
while (backstitches--)
680
if (s.device()->at()+13 > fileSize) return false;
681
Q_INT16 sx, sy, sp, ex, ey, ep;
689
m_pBackstitch->append(new BackStitch(QPoint(--sx*2+((sp-1)%3),--sy*2+((sp-1)/3)),QPoint(--ex*2+((ep-1)%3),--ey*2+((ep-1)/3)),color-1));
694
bool PatternCanvas::readKXStitchCanvas(QDataStream& s,int fileFormatVersion)
699
Q_INT32 backstitches;
706
QIODevice* device = s.device();
707
int fileSize = device->size();
708
switch (fileFormatVersion)
712
if (device->at()+8 > fileSize) return false;
713
s >> width >> height;
714
cells = width*height;
716
resize(width, height);
717
for (int i = 0 ; i < cells ; i++)
719
if (device->at()+1 > fileSize) return false;
723
Stitch::Queue* psq = new Stitch::Queue();
724
m_pCanvas->insert(i,psq);
727
if (device->at()+3 > fileSize) return false;
730
psq->enqueue(new Stitch((Stitch::Type)type, color));
734
if (device->at()+4 > fileSize) return false;
738
if (device->at()+10 > fileSize) return false;
740
m_pKnot->append(new Knot(start,color));
742
if (device->at()+4 > fileSize) return false;
744
while (backstitches--)
746
if (device->at()+17 > fileSize) return false;
747
s >> start >> end >> color;
748
m_pBackstitch->append(new BackStitch(start,end,color));
753
if (device->at()+8 > fileSize) return false;
754
s >> width >> height;
755
cells = width*height;
758
m_pBackstitch->clear();
759
resize(width, height);
760
for (int i = 0 ; i < cells ; i++)
762
if (device->at()+1 > fileSize) return false;
766
Stitch::Queue *psq = new Stitch::Queue();
767
m_pCanvas->insert(i,psq);
770
if (device->at()+3 > fileSize) return false;
773
if (type == Stitch::FRKnot)
774
addFrenchKnot(QPoint((i%width)*2+1,(i/width)*2+1),color);
776
psq->enqueue(new Stitch((Stitch::Type)type, color));
780
if (device->at()+4 > fileSize) return false;
782
while (backstitches--)
784
if (device->at()+10 > fileSize) return false;
785
s >> start >> end >> color;
786
m_pBackstitch->append(new BackStitch(start,end,color));
793
UsageMap PatternCanvas::calculateUsage()
795
// Calculate size of stitch cell relative to pattern properties
796
// and calculate relative size of a half stitch and quarter stitch
797
// Iterate canvas cells, iterating stitches in cell totalling the quantity of
799
// Required ClothCount, ClothCountUnits
801
const double halfStitch = 2.4142135; // using 2.4142135 = sqrt(2)+1 representing the diagonal of a cell + 1 side, e.g. /|/|/|/|
802
const double qtrStitch = 1.4142135; // using 1.4142135 = sqrt(2) representing corner to middle to another corner
803
int sq = m_pCanvas->size();
804
for (int i = 0 ; i < sq ; i++)
806
Stitch::Queue *q = m_pCanvas->at(i);
812
Stitch *s = q->dequeue();
814
// shouldn't really happen, but check for it anyway
822
usageMap[s->floss].quarter += 1;
823
usageMap[s->floss].stitchLength += qtrStitch;
827
usageMap[s->floss].half += 1;
828
usageMap[s->floss].stitchLength += halfStitch;
834
usageMap[s->floss].threeQuarter += 1;
835
usageMap[s->floss].stitchLength += (halfStitch+qtrStitch);
838
usageMap[s->floss].full += 1;
839
usageMap[s->floss].stitchLength += (halfStitch+halfStitch);
847
for (BackStitch *bs = m_pBackstitch->first() ; bs ; bs = m_pBackstitch->next())
849
QPoint p = bs->end - bs->start;
850
usageMap[bs->floss].backstitches += 1;
851
double manhattanLength = p.manhattanLength()/2.0;
852
usageMap[bs->floss].backstitchLength += manhattanLength;
854
for (Knot* k = m_pKnot->first() ; k ; k = m_pKnot->next())
856
usageMap[k->floss].knots += 1;
857
usageMap[k->floss].stitchLength += halfStitch; // estimated length of floss required for french knots