Pencil2D  ff90c0872e88be3bf81c548cd60f01983012ec49
Pencil2D is an animation software for both bitmap and vector graphics. It is free, multi-platform, and open source.
 All Classes Functions
bitmapimage.cpp
1 /*
2 
3 Pencil - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2017 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 #include <cmath>
18 #include "bitmapimage.h"
19 #include "util.h"
20 
21 BitmapImage::BitmapImage()
22 {
23  mImage = std::make_shared< QImage >(); // null image
24  mBounds = QRect( 0, 0, 0, 0 );
25 }
26 
27 BitmapImage::BitmapImage( const BitmapImage& a )
28 {
29  mBounds = a.mBounds;
30  mImage = std::make_shared< QImage >( *a.mImage );
31 }
32 
33 BitmapImage::BitmapImage( const QRect& rectangle, const QColor& colour)
34 {
35  mBounds = rectangle;
36  mImage = std::make_shared< QImage >( mBounds.size(), QImage::Format_ARGB32_Premultiplied);
37  mImage->fill(colour.rgba());
38 }
39 
40 BitmapImage::BitmapImage( const QRect& rectangle, const QImage& image )
41 {
42  mBounds = rectangle.normalized();
43  mExtendable = true;
44  mImage = std::make_shared< QImage >(image);
45  if ( mImage->width() != rectangle.width() || mImage->height() != rectangle.height())
46  {
47  qDebug() << "Error instancing bitmapImage.";
48  }
49 }
50 
51 BitmapImage::BitmapImage( const QString& path, const QPoint& topLeft )
52 {
53  mImage = std::make_shared< QImage >(path);
54  if ( mImage->isNull() )
55  {
56  qDebug() << "ERROR: Image " << path << " not loaded";
57  }
58  mBounds = QRect( topLeft, mImage->size() );
59 }
60 
61 BitmapImage::~BitmapImage()
62 {
63 }
64 
65 void BitmapImage::setImage( QImage* img )
66 {
67  Q_CHECK_PTR( img );
68  mImage.reset( img );
69 }
70 
71 BitmapImage& BitmapImage::operator=(const BitmapImage& a)
72 {
73  mBounds = a.mBounds;
74  mImage = std::make_shared< QImage >( *a.mImage );
75  return *this;
76 }
77 
78 void BitmapImage::paintImage(QPainter& painter)
79 {
80  painter.drawImage(topLeft(), *mImage);
81 }
82 
83 BitmapImage BitmapImage::copy()
84 {
85  return BitmapImage(mBounds, QImage(*mImage));
86 }
87 
88 BitmapImage BitmapImage::copy(QRect rectangle)
89 {
90  //QRect intersection = boundaries.intersected( rectangle );
91  QRect intersection2 = rectangle.translated( -topLeft() );
92  BitmapImage result = BitmapImage(rectangle, mImage->copy(intersection2));
93  return result;
94 }
95 
96 void BitmapImage::paste(BitmapImage* bitmapImage)
97 {
98  paste( bitmapImage, QPainter::CompositionMode_SourceOver );
99 }
100 
101 void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
102 {
103  QRect newBoundaries;
104  if ( mImage->width() == 0 || mImage->height() == 0 )
105  {
106  newBoundaries = bitmapImage->mBounds;
107  }
108  else
109  {
110  newBoundaries = mBounds.united( bitmapImage->mBounds );
111  }
112  extend( newBoundaries );
113 
114  QImage* image2 = bitmapImage->image();
115 
116  QPainter painter( mImage.get() );
117  painter.setCompositionMode(cm);
118  painter.drawImage( bitmapImage->mBounds.topLeft() - mBounds.topLeft(), *image2);
119  painter.end();
120 }
121 
122 void BitmapImage::add(BitmapImage* bitmapImage)
123 {
124  QImage* image2 = bitmapImage->image();
125 
126  QRect newBoundaries;
127  if ( mImage->width() == 0 || mImage->height() == 0 )
128  {
129  newBoundaries = bitmapImage->mBounds;
130  }
131  else
132  {
133  newBoundaries = mBounds.united( bitmapImage->mBounds );
134  }
135  extend( newBoundaries );
136  QPoint offset = bitmapImage->mBounds.topLeft() - mBounds.topLeft();
137  for (int y = 0; y < image2->height(); y++)
138  {
139  for (int x = 0; x < image2->width(); x++)
140  {
141  QRgb p1 = mImage->pixel(offset.x()+x,offset.y()+y);
142  QRgb p2 = image2->pixel(x,y);
143 
144  int a1 = qAlpha(p1);
145  int a2 = qAlpha(p2);
146  int r1 = qRed(p1);
147  int r2 = qRed(p2); // remember that the bitmap format is RGB32 Premultiplied
148  int g1 = qGreen(p1);
149  int g2 = qGreen(p2);
150  int b1 = qBlue(p1);
151  int b2 = qBlue(p2);
152 
153  // unite
154  int a = qMax(a1, a2);
155  int r = qMax(r1, r2);
156  int g = qMax(g1, g2);
157  int b = qMax(b1, b2);
158 
159  QRgb mix = qRgba(r, g, b, a);
160  if (a2 != 0)
161  {
162  mImage->setPixel(offset.x()+x,offset.y()+y, mix);
163  }
164  }
165  }
166 }
167 
168 void BitmapImage::compareAlpha(BitmapImage* bitmapImage) // this function picks the greater alpha value
169 {
170  QImage* image2 = bitmapImage->image();
171 
172  QRect newBoundaries;
173  if ( mImage->width() == 0 || mImage->height() == 0 )
174  {
175  newBoundaries = bitmapImage->mBounds;
176  }
177  else
178  {
179  newBoundaries = mBounds.united( bitmapImage->mBounds );
180  }
181  extend( newBoundaries );
182  QPoint offset = bitmapImage->mBounds.topLeft() - mBounds.topLeft();
183  for (int y = 0; y < image2->height(); y++)
184  {
185  for (int x = 0; x < image2->width(); x++)
186  {
187  QRgb p1 = mImage->pixel(offset.x()+x,offset.y()+y);
188  QRgb p2 = image2->pixel(x,y);
189 
190  int a1 = qAlpha(p1);
191  int a2 = qAlpha(p2);
192 
193  if (a1 <= a2)
194  {
195  QRgb mix = qRgba(qRed(p2), qGreen(p2), qBlue(p2), a2);
196  mImage->setPixel(offset.x()+x,offset.y()+y, mix);
197  }
198  }
199  }
200 }
201 
202 void BitmapImage::moveTopLeft(QPoint point)
203 {
204  mBounds.moveTopLeft(point);
205 }
206 
207 void BitmapImage::transform(QRect newBoundaries, bool smoothTransform)
208 {
209  mBounds = newBoundaries;
210  newBoundaries.moveTopLeft( QPoint(0,0) );
211  QImage* newImage = new QImage( mBounds.size(), QImage::Format_ARGB32_Premultiplied);
212  //newImage->fill(QColor(255,255,255).rgb());
213  QPainter painter(newImage);
214  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
215  painter.setCompositionMode(QPainter::CompositionMode_Source);
216  painter.fillRect( newImage->rect(), QColor(0,0,0,0) );
217  painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
218  painter.drawImage(newBoundaries, *mImage );
219  painter.end();
220  mImage.reset( newImage );
221 }
222 
223 BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool smoothTransform)
224 {
225  BitmapImage selectedPart = copy(selection);
226 
227  // Get the transformed image
228  //
229  QImage transformedImage;
230  if (smoothTransform)
231  {
232  transformedImage = selectedPart.image()->transformed(transform, Qt::SmoothTransformation);
233  }
234  else
235  {
236  transformedImage = selectedPart.image()->transformed(transform);
237  }
238 
239  return BitmapImage(transform.mapRect(selection), transformedImage);
240 }
241 
242 BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
243 {
244  BitmapImage transformedImage(newBoundaries, QColor(0,0,0,0));
245  QPainter painter(transformedImage.image());
246  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
247  newBoundaries.moveTopLeft( QPoint(0,0) );
248  painter.drawImage(newBoundaries, *mImage );
249  painter.end();
250  return transformedImage;
251 }
252 
253 
254 void BitmapImage::extend(QPoint P)
255 {
256  if (mBounds.contains( P ))
257  {
258  // nothing
259  }
260  else
261  {
262  extend( QRect(P, QSize(0,0)) ); // can we set QSize(0,0) ?
263  }
264 }
265 
266 void BitmapImage::extend(QRect rectangle)
267 {
268  if (!mExtendable) return;
269  if (rectangle.width() <= 0) rectangle.setWidth(1);
270  if (rectangle.height() <= 0) rectangle.setHeight(1);
271  if (mBounds.contains( rectangle ))
272  {
273  // nothing
274  }
275  else
276  {
277  QRect newBoundaries = mBounds.united(rectangle).normalized();
278  QImage* newImage = new QImage( newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
279  newImage->fill(Qt::transparent);
280  if (!newImage->isNull())
281  {
282  QPainter painter(newImage);
283  painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *mImage);
284  painter.end();
285  }
286  mImage.reset( newImage );
287  mBounds = newBoundaries;
288  }
289 }
290 
291 QRgb BitmapImage::pixel(int x, int y)
292 {
293  return pixel( QPoint(x,y) );
294 }
295 
296 QRgb BitmapImage::pixel(QPoint P)
297 {
298  QRgb result = qRgba(0,0,0,0); // black
299  if ( mBounds.contains( P ) ) result = mImage->pixel(P - topLeft());
300  return result;
301 }
302 
303 void BitmapImage::setPixel(int x, int y, QRgb colour)
304 {
305  setPixel( QPoint(x,y), colour);
306 }
307 
308 void BitmapImage::setPixel(QPoint P, QRgb colour)
309 {
310  extend( P );
311  if ( mBounds.contains(P) )
312  mImage->setPixel(P-topLeft(), colour);
313  //drawLine( QPointF(P), QPointF(P), QPen(QColor(colour)), QPainter::CompositionMode_SourceOver, false);
314 }
315 
316 
317 void BitmapImage::drawLine( QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
318 {
319  int width = 2+pen.width();
320  extend( QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width,-width,width,width) );
321  if (mImage != NULL && !mImage->isNull() )
322  {
323  QPainter painter( mImage.get() );
324  painter.setCompositionMode(cm);
325  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
326  painter.setPen(pen);
327  painter.drawLine( P1-topLeft(), P2-topLeft());
328  painter.end();
329  }
330 }
331 
332 void BitmapImage::drawRect( QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
333 {
334  int width = pen.width();
335  extend( rectangle.adjusted(-width,-width,width,width).toRect() );
336  if (brush.style() == Qt::RadialGradientPattern)
337  {
338  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
339  gradient->setCenter( gradient->center() - topLeft() );
340  gradient->setFocalPoint( gradient->focalPoint() - topLeft() );
341  }
342  if ( mImage && !mImage->isNull() )
343  {
344  QPainter painter( mImage.get() );
345  painter.setCompositionMode(cm);
346  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
347  painter.setPen(pen);
348  painter.setBrush(brush);
349  painter.drawRect( rectangle.translated(-topLeft()) );
350  painter.end();
351  }
352 }
353 
354 void BitmapImage::drawEllipse( QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
355 {
356  int width = pen.width();
357  extend( rectangle.adjusted(-width,-width,width,width).toRect() );
358  if (brush.style() == Qt::RadialGradientPattern)
359  {
360  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
361  gradient->setCenter( gradient->center() - topLeft() );
362  gradient->setFocalPoint( gradient->focalPoint() - topLeft() );
363  }
364  if ( mImage && !mImage->isNull() )
365  {
366  QPainter painter( mImage.get() );
367 
368  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
369  painter.setPen(pen);
370  painter.setBrush(brush);
371  //if (brush == Qt::NoBrush)
372 
373  painter.setCompositionMode(cm);
374  painter.drawEllipse( rectangle.translated(-topLeft()) );
375 
376  painter.end();
377  }
378 }
379 
380 void BitmapImage::drawPath( QPainterPath path, QPen pen, QBrush brush,
381  QPainter::CompositionMode cm, bool antialiasing)
382 {
383  int width = pen.width();
384  qreal inc = 1.0 + width / 20.0; // qreal?
385  //if (inc<1) { inc=1.0; }
386  extend( path.controlPointRect().adjusted(-width,-width,width,width).toRect() );
387 
388  if ( mImage != NULL && !mImage->isNull() )
389  {
390  QPainter painter( mImage.get() );
391  painter.setCompositionMode(cm);
392  painter.setRenderHint( QPainter::Antialiasing, antialiasing );
393  painter.setPen(pen);
394  painter.setBrush(brush);
395  painter.setTransform(QTransform().translate(-topLeft().x(), -topLeft().y()));
396  painter.setMatrixEnabled(true);
397  if (path.length() > 0)
398  {
399  for ( int pt = 0; pt < path.elementCount() - 1; pt++ )
400  {
401  qreal dx = path.elementAt(pt+1).x - path.elementAt(pt).x;
402  qreal dy = path.elementAt(pt+1).y - path.elementAt(pt).y;
403  qreal m = sqrt(dx*dx+dy*dy);
404  qreal factorx = dx / m;
405  qreal factory = dy / m;
406  for ( float h = 0.f; h < m; h += inc )
407  {
408  qreal x = path.elementAt(pt).x + factorx * h;
409  qreal y = path.elementAt(pt).y + factory * h;
410  painter.drawPoint( QPointF( x, y ) );
411  }
412  }
413 
414  //painter.drawPath( path );
415  }
416  else
417  {
418  // forces drawing when points are coincident (mousedown)
419  painter.drawPoint( path.elementAt(0).x, path.elementAt(0).y );
420  }
421  painter.end();
422  }
423 }
424 
425 void BitmapImage::clear()
426 {
427  mImage = std::make_shared< QImage >(); // null image
428  mBounds = QRect(0,0,0,0);
429 }
430 
431 QRgb BitmapImage::constScanLine(int x, int y) {
432  QRgb result = qRgba( 0, 0, 0, 0 );
433  if ( mBounds.contains( QPoint( x, y ) ) ) {
434  result = *( reinterpret_cast< const QRgb* >( mImage->constScanLine( y - topLeft().y() ) ) + x - topLeft().x() );
435  }
436 
437  return result;
438 }
439 
440 void BitmapImage::scanLine(int x, int y, QRgb colour)
441 {
442  extend( QPoint( x, y ) );
443  if( mBounds.contains( QPoint( x, y ) ) ) {
444 
445  // Make sure color is premultiplied before calling
446  *( reinterpret_cast< QRgb* >( mImage->scanLine( y - topLeft().y() ) ) + x - topLeft().x() ) =
447  qRgba(
448  qRed( colour ),
449  qGreen( colour ),
450  qBlue( colour ),
451  qAlpha( colour ) );
452  }
453 }
454 
455 void BitmapImage::clear(QRect rectangle)
456 {
457  QRect clearRectangle = mBounds.intersected( rectangle );
458  clearRectangle.moveTopLeft( clearRectangle.topLeft() - topLeft() );
459 
460  QPainter painter( mImage.get() );
461  painter.setCompositionMode(QPainter::CompositionMode_Clear);
462  painter.fillRect( clearRectangle, QColor(0,0,0,0) );
463  painter.end();
464 }
465 
466 int BitmapImage::pow(int n) // pow of a number
467 {
468  return n*n;
469 }
470 
471 bool BitmapImage::compareColor(QRgb color1, QRgb color2, int tolerance)
472 {
473  if ( color1 == color2 ) return true;
474 
475  int red1 = qRed( color1 );
476  int green1 = qGreen( color1 );
477  int blue1 = qBlue( color1 );
478  int alpha1 = qAlpha( color1 );
479 
480  int red2 = qRed( color2 );
481  int green2 = qGreen( color2 );
482  int blue2 = qBlue( color2 );
483  int alpha2 = qAlpha( color2 );
484 
485  int diffRed = abs( red2 - red1 );
486  int diffGreen = abs( green2 - green1 );
487  int diffBlue = abs( blue2 - blue1 );
488  int diffAlpha = abs( alpha2 - alpha1 );
489 
490  if ( diffRed > tolerance ||
491  diffGreen > tolerance ||
492  diffBlue > tolerance ||
493  diffAlpha > tolerance )
494  {
495  return false;
496  }
497 
498  return true;
499 }
500 
501 // Flood fill
502 // ----- http://lodev.org/cgtutor/floodfill.html
503 void BitmapImage::floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, QRgb oldColor, QRgb newColor, int tolerance)
504 {
505  if ( oldColor == newColor ){
506  return;
507  }
508 
509  oldColor = targetImage->pixel( point );
510  oldColor = qRgba( qRed( oldColor ), qGreen( oldColor ), qBlue( oldColor ), qAlpha( oldColor ) );
511 
512  // Preparations
513  QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
514 
515  BitmapImage* replaceImage = nullptr;
516  QPoint tempPoint;
517  QRgb newPlacedColor;
518 
519  int xTemp;
520  bool spanLeft, spanRight;
521 
522  // Extend to size of Camera
523  targetImage->extend( cameraRect );
524  replaceImage = new BitmapImage( cameraRect, Qt::transparent );
525 
526  queue.append( point );
527  // Preparations END
528 
529  while( !queue.empty() ) {
530  tempPoint = queue.takeFirst();
531 
532  point.setX( tempPoint.x() );
533  point.setY( tempPoint.y() );
534 
535  xTemp = point.x();
536 
537  newPlacedColor = replaceImage->constScanLine( xTemp, point.y() );
538  while( xTemp >= targetImage->topLeft().x() &&
539  compareColor( targetImage->constScanLine( xTemp, point.y() ), oldColor, tolerance) ) xTemp--;
540  xTemp++;
541 
542  spanLeft = spanRight = false;
543  while( xTemp <= targetImage->right() &&
544  compareColor( targetImage->constScanLine( xTemp, point.y() ), oldColor, tolerance ) &&
545  newPlacedColor != newColor ) {
546 
547  // Set pixel color
548  replaceImage->scanLine( xTemp, point.y(), newColor );
549 
550  if( !spanLeft && (point.y() > targetImage->top() ) &&
551  compareColor( targetImage->constScanLine( xTemp, point.y() - 1 ), oldColor, tolerance ) ) {
552  queue.append( QPoint( xTemp, point.y() - 1) );
553  spanLeft = true;
554  } else if( spanLeft && ( point.y() > targetImage->top() ) &&
555  !compareColor( targetImage->constScanLine( xTemp, point.y() - 1 ), oldColor, tolerance ) ) {
556  spanLeft = false;
557  }
558 
559  if( !spanRight && point.y() < targetImage->bottom() &&
560  compareColor( targetImage->constScanLine( xTemp, point.y() + 1 ), oldColor, tolerance ) ) {
561  queue.append( QPoint( xTemp, point.y() + 1 ) );
562  spanRight = true;
563 
564  } else if( spanRight && point.y() < targetImage->bottom() &&
565  !compareColor( targetImage->constScanLine( xTemp, point.y() + 1 ), oldColor, tolerance ) ) {
566  spanRight = false;
567  }
568 
569  Q_ASSERT( queue.count() < ( targetImage->width() * targetImage->height() ) );
570  xTemp++;
571  }
572  }
573 
574  targetImage->paste( replaceImage );
575  delete replaceImage;
576 }