
This was hinted at last month, so here it is.
This was actually going to be part of a larger project, a matplotlib color map maker (which in turn is part of an even larger project), but the more I worked with it, I didn’t think it’s that useful. Plus it kind of made the whole thing look odd:

In progress look of the colormap maker
But I don’t want it to go to waste, so I’m posting it here and passing the savings on to you!
The color harmonies, or schemes are just various rotations around the color wheel; in this code, the hue, Saturation and value wheel – where the hue is the angle of the wheel. So you get your h, s, v values of your selected color, add the rotation degree to the hue and set the new color those values (leaving the s and v the same). Easy peasy!
* A complementary color is 180 degrees away from your original color (base).
* Analogous colors are ±30° from base.
* Split-complementary colors are analogous colors of the complementary, or ± 150° from base.
* Triadic colors form a triangle on the circle (thus the name!) They’re ±120° from base.
* Square colors are (naturally) a square on the circle, 180° and ±90° from base.
* Tetradic colors form a rectangle from two complementary colors – the second coming from an analogous of an analogous – or 180°, 60° and -120° from base. The square colors may also be called tetradic.
Also included are tints, tones and shades. Tints are the base color mixed with white, tones are the base mixed with gray and shades are the base mixed with black.
As for the code, it works with PyQt5/Pyside2. You set a color with setColor, which it have it calculate all the other harmonious colors. It’ll emit a harmonyChanged signal that’s a dictionary of QColor lists, keyed by their name (complementary, triadic, etc.). You may also get them via getHarmony or currentHarmony. The color you’ve currently selected within its tables is emitted by the colorChanged signal and can be gotten by getColor or currentColor. I might still use this, just not sure where though. In the mean time, here’s a block of code free of charge:
from PySide2.QtCore import Qt, Signal#pyqtSignal as Signal for PyQt5
from PySide2.QtGui import QColor
from PySide2.QtWidgets import (QAbstractItemView, QApplication, QFrame,
QGridLayout, QLabel, QTableWidget,
QTableWidgetItem, QWidget)
class colorHarmony(QWidget):
"color harmony/scheme viewer"
colorChanged = Signal(QColor)
harmonyChanged = Signal(dict)
def __init__(self, initial=None, parent=None):
super(colorHarmony, self).__init__(parent)
self.setup(); self.setColor(initial)
def _colorSelected(self):
"internal color selection; emits color changed signal"
table = self.sender()
if table not in self.tables: return
item = table.currentItem()
try: col = item.backgroundColor() # PySide2
except AttributeError: col = item.background().color() # PyQt5
for t in self.tables:
if t is table: continue
t.blockSignals(True); t.clearSelection(); t.blockSignals(False)
self._color = col
self.colorChanged.emit(col)
#print(str(col.name()))
def currentColor(self): return self._color
def getColor(self): return self._color
def setColor(self, color=None):
"set main color, calculate color schemes"
tint, tone = self.tint, self.tone
shad, alph = self.shad, self.alph
clr = {}
if color is None: col = QColor('#ffffff')
else: col = QColor(color)
h, s, v, a = col.getHsv()
self._color = col
# complementary color
com = QColor(); com.setHsv((h + 180)%360, s, v)
clr['complementary'] = [col, com]
# split-complementary colors
spl1 = QColor(); spl1.setHsv((h + 210)%360, s, v)
spl2 = QColor(); spl2.setHsv((h + 150)%360, s, v)
clr['split'] = [col, spl1, spl2]
# analogous colors
ana1 = QColor(); ana1.setHsv((h + 30)%360, s, v)
ana2 = QColor(); ana2.setHsv((h - 30)%360, s, v)
clr['analogous'] = [col, ana1, ana2]
# triadic colors
tri1 = QColor(); tri1.setHsv((h + 120)%360, s, v)
tri2 = QColor(); tri2.setHsv((h - 120)%360, s, v)
clr['triadic'] = [col, tri1, tri2]
# square tetradic colors
tet1 = QColor(); tet1.setHsv((h + 180)%360, s, v)
tet2 = QColor(); tet2.setHsv((h + 90)%360, s, v)
tet3 = QColor(); tet3.setHsv((h - 90)%360, s, v)
clr['square'] = [col, tet1, tet2, tet3]
# rectangle tetradic colors
rec1 = QColor(); rec1.setHsv((h + 60)%360, s, v)
rec2 = QColor(); rec2.setHsv((h + 180)%360, s, v)
rec3 = QColor(); rec3.setHsv((h - 120)%360, s, v)
clr['tetradic'] = [col, rec1, rec2, rec3]
# tints (add white)
clr['tint'] = [col, ]
for i in range(5):
#tmp = clr['tint'][-1].lighter(115)
#clr['tint'].append(tmp)
r, g, b, a = clr['tint'][-1].getRgb()
tmp = QColor(r + (tint - r) * alph,
g + (tint - g) * alph,
b + (tint - b) * alph)
clr['tint'].append(tmp)
# tones (add grey)
clr['tone'] = [col, ]
for i in range(5):
r, g, b, a = clr['tone'][-1].getRgb()
tmp = QColor(r + (tone - r) * alph,
g + (tone - g) * alph,
b + (tone - b) * alph)
clr['tone'].append(tmp)
# shades (add black)
clr['shade'] = [col, ]
for i in range(5):
#tmp = clr['shade'][-1].darker(115)
#clr['shade'].append(tmp)
r, g, b, a = clr['shade'][-1].getRgb()
tmp = QColor(r + (shad - r) * alph,
g + (shad - g) * alph,
b + (shad - b) * alph)
clr['shade'].append(tmp)
empty = [QColor(), ] * 7
for i in self.tables:
i.clear()
tmp = clr.get(self.tableNames[i], empty)
for r in range(i.columnCount()):
c = tmp[r]; item = QTableWidgetItem()
try: item.setBackgroundColor(c) # PySide2
except AttributeError: item.setBackground(c) # PyQt5
i.setItem(0, r, item)
self._hrmy = clr
self.harmonyChanged.emit(clr)
def currentHarmony(self): return self._hrmy
def getHarmony(self): return self._hrmy
def setup(self):
rightCenter = (Qt.AlignRight | Qt.AlignVCenter)
self.tableNames = {}; self._color = self._hrmy = None
# values used in determining tints, tones shades
self.tint, self.tone, self.shad = 255, 128, 0 # 0 - 255
self.alph = 0.17 # 0 - 1
itemSize = 16 # color swatch size
# column 1
self.comLabel = QLabel("&Complementary:")
self.comTable = QTableWidget()
self.comLabel.setToolTip("Complementary color scheme")
self.comLabel.setBuddy(self.comTable)
self.comLabel.setAlignment(rightCenter)
self.comTable.setColumnCount(2)
self.tableNames[self.comTable] = 'complementary'
self.splLabel = QLabel("&Split:")
self.splLabel.setToolTip("Split-complementary color scheme")
self.splTable = QTableWidget()
self.splLabel.setBuddy(self.splTable)
self.splLabel.setAlignment(rightCenter)
self.splTable.setColumnCount(3)
self.tableNames[self.splTable] = 'split'
self.anaLabel = QLabel("&Analogous:")
self.anaLabel.setToolTip("Analogous color scheme")
self.anaTable = QTableWidget()
self.anaLabel.setBuddy(self.anaTable)
self.anaLabel.setAlignment(rightCenter)
self.anaTable.setColumnCount(3)
self.tableNames[self.anaTable] = 'analogous'
# column 2
self.triLabel = QLabel("&Triadic:")
self.triTable = QTableWidget()
self.triLabel.setToolTip("Triadic color scheme")
self.triLabel.setBuddy(self.triTable)
self.triLabel.setAlignment(rightCenter)
self.triTable.setColumnCount(3)
self.tableNames[self.triTable] = 'triadic'
self.tetLabel = QLabel("T&etradic:")
self.tetLabel.setToolTip("Tetradic (rectangle) color scheme")
self.tetTable = QTableWidget()
self.tetLabel.setBuddy(self.tetTable)
self.tetLabel.setAlignment(rightCenter)
self.tetTable.setColumnCount(4)
self.tableNames[self.tetTable] = 'tetradic'
self.squLabel = QLabel("S&quare:")
self.squLabel.setToolTip("Square color scheme")
self.squTable = QTableWidget()
self.squLabel.setBuddy(self.squTable)
self.squLabel.setAlignment(rightCenter)
self.squTable.setColumnCount(4)
self.tableNames[self.squTable] = 'square'
# column 3
self.tinLabel = QLabel("T&ints:")
self.tinLabel.setToolTip("color tints (+white)")
self.tinTable = QTableWidget()
self.tinLabel.setBuddy(self.tinTable)
self.tinLabel.setAlignment(rightCenter)
self.tinTable.setColumnCount(6)
self.tableNames[self.tinTable] = 'tint'
self.tonLabel = QLabel("T&ones:")
self.tonLabel.setToolTip("color tones (+gray)")
self.tonTable = QTableWidget()
self.tonLabel.setBuddy(self.tonTable)
self.tonLabel.setAlignment(rightCenter)
self.tonTable.setColumnCount(6)
self.tableNames[self.tonTable] = 'tone'
self.shaLabel = QLabel("S&hades:")
self.shaLabel.setToolTip("color shades (+black)")
self.shaTable = QTableWidget()
self.shaLabel.setBuddy(self.shaTable)
self.shaLabel.setAlignment(rightCenter)
self.shaTable.setColumnCount(6)
self.tableNames[self.shaTable] = 'shade'
self.labels = [self.comLabel, self.splLabel, self.anaLabel,
self.triLabel, self.tetLabel, self.squLabel,
self.tinLabel, self.tonLabel, self.shaLabel, ]
#for i in self.labels: i.setFrameShape(QFrame.Box)
#for i in self.labels: i.setFixedHeight(itemSize)
self.tables = [self.comTable, self.splTable, self.anaTable,
self.triTable, self.tetTable, self.squTable,
self.tinTable, self.tonTable, self.shaTable, ]
for i in self.tables: # set all table settings
i.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
i.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
i.setSelectionMode(QAbstractItemView.SingleSelection)
i.setEditTriggers(QAbstractItemView.NoEditTriggers)
i.horizontalHeader().setVisible(False)
i.verticalHeader().setVisible(False)
i.horizontalHeader().setDefaultSectionSize(itemSize)
i.verticalHeader().setDefaultSectionSize(itemSize)
i.horizontalHeader().setMinimumSectionSize(itemSize)
i.verticalHeader().setMinimumSectionSize(itemSize)
i.setShowGrid(False)
i.setFrameShape(QFrame.NoFrame)#Box)
i.setRowCount(1); i.setRowHeight(0, itemSize)
for r in range(i.columnCount()):
i.setColumnWidth(r, itemSize)
i.setItem(0, r, QTableWidgetItem())
i.setFixedWidth(i.columnCount() * itemSize)
i.setFixedHeight(itemSize)
i.itemSelectionChanged.connect(self._colorSelected)
# layout
grid = QGridLayout(); grid.setSpacing(5)
grid.addWidget(self.comLabel, 0, 0)
grid.addWidget(self.comTable, 0, 1)
grid.addWidget(self.splLabel, 1, 0)
grid.addWidget(self.splTable, 1, 1)
grid.addWidget(self.anaLabel, 2, 0)
grid.addWidget(self.anaTable, 2, 1)
grid.addWidget(self.triLabel, 0, 2)
grid.addWidget(self.triTable, 0, 3)
grid.addWidget(self.tetLabel, 1, 2)
grid.addWidget(self.tetTable, 1, 3)
grid.addWidget(self.squLabel, 2, 2)
grid.addWidget(self.squTable, 2, 3)
grid.addWidget(self.tinLabel, 0, 4)
grid.addWidget(self.tinTable, 0, 5)
grid.addWidget(self.tonLabel, 1, 4)
grid.addWidget(self.tonTable, 1, 5)
grid.addWidget(self.shaLabel, 2, 4)
grid.addWidget(self.shaTable, 2, 5)
self.setLayout(grid)
self.resize(self.sizeHint())
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
widg = colorHarmony()
widg.show()
widg.setColor(QColor('#fec615'))
sys.exit(app.exec_())
Leave a comment