Pencil2D  ff90c0872e88be3bf81c548cd60f01983012ec49
Pencil2D is an animation software for both bitmap and vector graphics. It is free, multi-platform, and open source.
 All Classes Functions
colorpalettewidget.cpp
1 /*
2 
3 Pencil - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2013-2017 Matt 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 <QDebug>
18 #include <QListWidget>
19 #include <QListWidgetItem>
20 #include <QVBoxLayout>
21 #include <QInputDialog>
22 #include <QColorDialog>
23 #include "ui_colorpalette.h"
24 
25 #include "colordictionary.h"
26 #include "colourref.h"
27 #include "object.h"
28 #include "editor.h"
29 #include "colorbox.h"
30 #include "scribblearea.h"
31 #include "colormanager.h"
32 #include "colorpalettewidget.h"
33 
34 
35 
36 ColorPaletteWidget::ColorPaletteWidget( QWidget* parent ) : BaseDockWidget( parent )
37 {
38  setWindowTitle( tr( "Color Palette", "Window title of color palette." ) );
39 
40  QWidget* pWidget = new QWidget( this );
41  ui = new Ui::ColorPalette;
42  ui->setupUi( pWidget );
43  setWidget( pWidget );
44 
45  connect( ui->colorListWidget, &QListWidget::currentItemChanged,
46  this, &ColorPaletteWidget::colorListCurrentItemChanged );
47 
48  connect( ui->colorListWidget, SIGNAL( itemClicked( QListWidgetItem* ) ),
49  this, SLOT( clickColorListItem( QListWidgetItem* ) ) );
50 
51  connect( ui->colorListWidget, SIGNAL( itemDoubleClicked( QListWidgetItem* ) ), this,
52  SLOT( changeColourName( QListWidgetItem* ) ) );
53 
54  connect( ui->addColorButton, &QPushButton::clicked, this, &ColorPaletteWidget::clickAddColorButton );
55  connect( ui->removeColorButton, &QPushButton::clicked, this, &ColorPaletteWidget::clickRemoveColorButton );
56  connect( ui->palettePref, &QToolButton::clicked, this, &ColorPaletteWidget::palettePreferences );
57 }
58 
59 void ColorPaletteWidget::initUI()
60 {
61  // "Remove color" feature is disabled because
62  // vector strokes that are linked to palette
63  // colors don't handle color removal from palette
64  //
65  iconSize = QSize(34,34);
66  ui->removeColorButton->hide();
67  updateUI();
68  palettePreferences();
69 }
70 
71 void ColorPaletteWidget::updateUI()
72 {
73  refreshColorList();
74  updateGridUI();
75 }
76 
77 void ColorPaletteWidget::setColor(QColor newColor)
78 {
79  int colorIndex = currentColourNumber();
80  updateItemColor(colorIndex, newColor);
81 
82  emit colorChanged( newColor );
83 }
84 
85 void ColorPaletteWidget::selectColorNumber(int colorNumber)
86 {
87  ui->colorListWidget->setCurrentRow(colorNumber);
88 }
89 
90 int ColorPaletteWidget::currentColourNumber()
91 {
92  if ( ui->colorListWidget->currentRow() < 0 )
93  {
94  ui->colorListWidget->setCurrentRow( 0 );
95  }
96  return ui->colorListWidget->currentRow();
97 }
98 
99 void ColorPaletteWidget::refreshColorList()
100 {
101 
102  if ( ui->colorListWidget->count() > 0)
103  {
104  ui->colorListWidget->clear();
105  }
106 
107  QPixmap originalColourSwatch( iconSize );
108  QPainter swatchPainter( &originalColourSwatch );
109  swatchPainter.drawTiledPixmap( 0, 0, iconSize.width(), iconSize.height(), QPixmap( ":/background/checkerboard.png" ) );
110  swatchPainter.end();
111  QPixmap colourSwatch;
112 
113  QListWidgetItem* colourItem;
114  ColourRef colourRef;
115  for (int i = 0; i < editor()->object()->getColourCount(); i++)
116  {
117  colourRef = editor()->object()->getColour(i);
118 
119  colourItem = new QListWidgetItem( ui->colorListWidget );
120 
121  if ( ui->colorListWidget->viewMode() != QListView::IconMode){
122  colourItem->setText( colourRef.name );
123  }
124  colourSwatch = originalColourSwatch;
125  swatchPainter.begin( &colourSwatch );
126  swatchPainter.fillRect( 0, 0, iconSize.width(), iconSize.height(), colourRef.colour );
127  swatchPainter.end();
128  colourItem->setIcon( colourSwatch );
129 
130  ui->colorListWidget->addItem( colourItem );
131  }
132  update();
133 }
134 
135 void ColorPaletteWidget::colorListCurrentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
136 {
137  if (!current)
138  {
139  current = previous;
140  }
141  emit colorNumberChanged( ui->colorListWidget->row(current) );
142 }
143 
144 void ColorPaletteWidget::clickColorListItem(QListWidgetItem* currentItem)
145 {
146  int colorIndex = ui->colorListWidget->row( currentItem );
147  //m_pEditor->selectAndApplyColour( colorIndex );
148 
149  emit colorNumberChanged( colorIndex );
150 }
151 
152 void ColorPaletteWidget::changeColourName( QListWidgetItem* item )
153 {
154  Q_ASSERT( item != NULL );
155 
156  int colorNumber = ui->colorListWidget->row( item );
157  if (colorNumber > -1)
158  {
159  bool ok;
160  QString text = QInputDialog::getText(this,
161  tr("Colour name"),
162  tr("Colour name:"),
163  QLineEdit::Normal,
164  editor()->object()->getColour(colorNumber).name,
165  &ok );
166  if (ok && !text.isEmpty())
167  {
168  editor()->object()->renameColour(colorNumber, text);
169  refreshColorList();
170  }
171  }
172 }
173 
174 void ColorPaletteWidget::palettePreferences()
175 {
176  layoutModes = new QActionGroup(this);
177  listMode = new QAction (tr("List mode"), this);
178  listMode->setStatusTip(tr("Show palette as a list"));
179  listMode->setCheckable(true);
180  listMode->setChecked(true);
181  layoutModes->addAction(listMode);
182  connect(listMode, &QAction::triggered, this, &ColorPaletteWidget::setListMode);
183 
184  gridMode = new QAction (tr("Grid mode"), this);
185  gridMode->setStatusTip(tr("Show palette as icons"));
186  gridMode->setCheckable(true);
187  layoutModes->addAction(gridMode);
188  connect(gridMode, &QAction::triggered, this, &ColorPaletteWidget::setGridMode);
189 
190  // Swatch size control
191  iconSizes = new QActionGroup (this);
192  smallSwatch = new QAction(tr("Small swatch"), this);
193  smallSwatch->setStatusTip(tr("Sets swatch size to: 16x16px"));
194  smallSwatch->setCheckable(true);
195  iconSizes->addAction(smallSwatch);
196  connect(smallSwatch, &QAction::triggered, this, &ColorPaletteWidget::setSwatchSizeSmall);
197 
198  mediumSwatch = new QAction(tr("Medium swatch"), this);
199  mediumSwatch->setStatusTip(tr("Sets swatch size to: 26x26px"));
200  mediumSwatch->setCheckable(true);
201  iconSizes->addAction(mediumSwatch);
202  connect(mediumSwatch, &QAction::triggered, this, &ColorPaletteWidget::setSwatchSizeMedium);
203 
204  largeSwatch = new QAction(tr("Large swatch"), this);
205  largeSwatch->setCheckable(true);
206  largeSwatch->setStatusTip(tr("Sets swatch size to: 36x36px"));
207  iconSizes->addAction(largeSwatch);
208  largeSwatch->setChecked(true);
209  connect(largeSwatch, &QAction::triggered, this, &ColorPaletteWidget::setSwatchSizeLarge);
210 
211  ui->colorListWidget->setMinimumWidth(ui->colorListWidget->sizeHintForColumn(0));
212 
213  // Let's pretend this button is a separator
214  separator = new QAction(tr(""), this);
215  separator->setSeparator(true);
216 
217  // Add to UI
218  ui->palettePref->addAction(listMode);
219  ui->palettePref->addAction(gridMode);
220  ui->palettePref->addAction(separator);
221  ui->palettePref->addAction(smallSwatch);
222  ui->palettePref->addAction(mediumSwatch);
223  ui->palettePref->addAction(largeSwatch);
224 }
225 
226 void ColorPaletteWidget::setListMode()
227 {
228  ui->colorListWidget->setViewMode(QListView::ListMode);
229  ui->colorListWidget->setMovement(QListView::Static);
230  ui->colorListWidget->setGridSize(QSize(-1,-1));
231  updateUI();
232 }
233 
234 void ColorPaletteWidget::setGridMode()
235 {
236  ui->colorListWidget->setViewMode(QListView::IconMode);
237  ui->colorListWidget->setMovement(QListView::Static); // TODO: update swatch index on move
238  ui->colorListWidget->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
239  ui->colorListWidget->setGridSize(iconSize);
240  updateUI();
241 }
242 
243 void ColorPaletteWidget::resizeEvent(QResizeEvent *event)
244 {
245  // Find the value to divivde with
246  for (int i = 1; i < 75; i++) {
247  int size = (ui->colorListWidget->width()-18) / i; // subtract scrollbar width
248  if (size >= iconSize.width() && size <= iconSize.width() + 8){
249  stepper = size;
250  }
251  }
252  QSize tempSize = QSize(stepper,iconSize.height());
253 
254  ui->colorListWidget->setIconSize(QSize(tempSize.width(),iconSize.height()));
255  ui->colorListWidget->setGridSize(QSize(tempSize.width(),iconSize.height()));
256  iconSize.setWidth(iconSize.width());
257 
258  refreshColorList();
259  QWidget::resizeEvent(event);
260 }
261 
262 void ColorPaletteWidget::setSwatchSizeSmall()
263 {
264  if (iconSize.width() > 18) {
265  iconSize = QSize(14,14);
266  }
267  updateUI();
268 }
269 
270 void ColorPaletteWidget::setSwatchSizeMedium()
271 {
272  if (iconSize.width() < 20 || iconSize.width() > 30) {
273  iconSize = QSize(26,26);
274  updateUI();
275  }
276 }
277 
278 void ColorPaletteWidget::setSwatchSizeLarge()
279 {
280  if (iconSize.width() < 30) {
281  iconSize = QSize(34,34);
282  updateUI();
283  }
284 }
285 
286 void ColorPaletteWidget::updateGridUI()
287 {
288  if (ui->colorListWidget->viewMode() == QListView::IconMode)
289  {
290  ui->colorListWidget->setGridSize(QSize(iconSize.width(),iconSize.height()));
291  }
292  else {
293 
294  ui->colorListWidget->setGridSize(QSize(-1,-1));
295  }
296  ui->colorListWidget->setIconSize(iconSize);
297 }
298 
299 QString ColorPaletteWidget::getDefaultColorName(QColor c)
300 {
301  // Separate rgb values for convenience
302  const int r = c.red(),
303  g = c.green(),
304  b = c.blue();
305 
306  // Convert RGB to XYZ with D65 white point
307  // (algorithm source: https://www.cs.rit.edu/%7Encs/color/t_convert.html#RGB%20to%20XYZ%20&%20XYZ%20to%20RGB)
308  const qreal x = 0.412453*r + 0.357580*g + 0.180423*b,
309  y = 0.212671*r + 0.715160*g + 0.072169*b,
310  z = 0.019334*r + 0.119193*g + 0.950227*b;
311 
312  // Convert XYZ to CEI L*u*v
313  // (algorithm source: https://www.cs.rit.edu/~ncs/color/t_convert.html#XYZ%20to%20CIE%20L*a*b*%20(CIELAB)%20&%20CIELAB%20to%20XYZ)
314  // Helper function for the conversion
315  auto f = [] (const double a) { return a > 0.008856 ? cbrt(a) : 7.787*a + 16/116; };
316  // XYZ tristimulus values for D65 (taken from: https://en.wikipedia.org/wiki/Illuminant_D65#Definition)
317  const qreal xn = 95.047,
318  yn = 100,
319  zn = 108.883;
320  const qreal l = y/yn > 0.008856 ? 116*cbrt( y/yn ) - 16 : 903.3*y/yn,
321  u = 500 * ( f( x/xn ) - f( y/yn ) ),
322  v = 200 * ( f( y/yn ) - f( z/zn ) );
323 
324  // Find closest color match in colorDict to the luv values
325  int minLoc = 0;
326  if(u < 0.01 && u > -0.01 && v < 0.01 && v > -0.01) {
327  // The color is grayscale so only compare to gray centroids so there is no 'false hue'
328  qreal minDist = pow(colorDict[dictSize-5][0] - l, 2) + pow(colorDict[dictSize-5][1] - u, 2) + pow(colorDict[dictSize-5][2] - v, 2);
329  qreal curDist;
330  for ( int i = dictSize-4; i < dictSize; i++)
331  {
332  curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
333  if ( curDist < minDist )
334  {
335  minDist = curDist;
336  minLoc = i;
337  }
338  }
339  }
340  else {
341  qreal minDist = pow(colorDict[0][0] - l, 2) + pow(colorDict[0][1] - u, 2) + pow(colorDict[0][2] - v, 2);
342  qreal curDist;
343  for ( int i = 1; i < dictSize; i++)
344  {
345  curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
346  if ( curDist < minDist )
347  {
348  minDist = curDist;
349  minLoc = i;
350  }
351  }
352  }
353  return nameDict[minLoc];
354 }
355 
356 void ColorPaletteWidget::clickAddColorButton()
357 {
358  QColor prevColor = Qt::white;
359 
360  if ( currentColourNumber() > -1 )
361  {
362  prevColor = editor()->object()->getColour(currentColourNumber()).colour;
363  }
364 
365  QColor newColour = QColorDialog::getColor( prevColor.rgba(), this, QString(), QColorDialog::ShowAlphaChannel );
366  if ( !newColour.isValid() )
367  {
368  // User cancelled operation
369  return;
370  }
371 
372  ColourRef ref(newColour);
373  ref.name = getDefaultColorName(newColour);
374 
375  editor()->object()->addColour(ref);
376  refreshColorList();
377 
378  int colorIndex = editor()->object()->getColourCount() - 1;
379 
380  editor()->color()->setColorNumber(colorIndex);
381  editor()->color()->setColor( ref.colour );
382 }
383 
384 void ColorPaletteWidget::clickRemoveColorButton()
385 {
386  int colorNumber = ui->colorListWidget->currentRow();
387  editor()->object()->removeColour(colorNumber);
388 
389  refreshColorList();
390 }
391 
392 void ColorPaletteWidget::updateItemColor( int itemIndex, QColor newColor )
393 {
394  QPixmap colourSwatch( iconSize );
395  QPainter swatchPainter( &colourSwatch );
396  swatchPainter.drawTiledPixmap( 0, 0, iconSize.width(), iconSize.height(), QPixmap( ":/background/checkerboard.png" ) );
397  swatchPainter.fillRect( 0, 0, iconSize.width(), iconSize.height(), newColor );
398  ui->colorListWidget->item( itemIndex )->setIcon( colourSwatch );
399 
400  // Make sure to update grid in grid mode
401  if (ui->colorListWidget->viewMode() == QListView::IconMode)
402  {
403  updateGridUI();
404  }
405 }