Color Harmony Display

charmony
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:

colormap_maker

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_())

Tagged with: , ,
Posted in Python

Leave a comment

In Archive
Design a site like this with WordPress.com
Get started