Source code for colorpicker

from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QRegExp, QEvent
from PyQt5.QtGui import (
    QColor,
    QValidator,
    QPixmap,
    QFont,
    QRegExpValidator,
    QPainter,
    QPen,
)
from PyQt5.QtWidgets import (
    QLabel,
    QFrame,
    QDialog,
    QVBoxLayout,
    QGridLayout,
    QDialogButtonBox,
    QHBoxLayout,
    QFormLayout,
    QLineEdit,
    QLayout,
)


[docs]def upsample(red, green, blue): """Upsamples RGB from 8-bit to 24-bit :param red: 8-bit red value :type red: int :param green: 8-bit green value :type green: int :param blue: 8-bit blue value :type blue: int :return: 24-bit upscaled RGB tuple :rtype: tuple(int, int, int) """ red = round((red / 7) * 255) green = round((green / 7) * 255) blue = round((blue / 3) * 255) return (red, green, blue)
[docs]def downsample(red, green, blue): """Downsamples RGB from 24-bit to 8-bit :param red: 24-bit red value :type red: int :param green: 24-bit green value :type green: int :param blue: 24-bit blue value :type blue: int :return: 8-bit downscaled RGB tuple :rtype: tuple(int, int, int) """ red = round((red / 255) * 7) green = round((green / 255) * 7) blue = round((blue / 255) * 3) return (red, green, blue)
[docs]def normalize(red, green, blue): """Normalizes any RGB value to be representable by a whole 8-bit value :param red: Red value :type red: int :param green: Green value :type green: int :param blue: Blue value :type blue: int :return: 24-bit normalized RGB tuple :rtype: tuple(int, int, int) """ return upsample(*downsample(red, green, blue))
[docs]class Color(QLabel): """Represents a single color tile in the color picker :param index: Index of the color tile :type index: int :param parent: Parent widget, defaults to None :type parent: QWidget, optional """ color = QColor() clicked = pyqtSignal(QColor) color_selected = pyqtSignal(int) def __init__(self, index, parent=None): QLabel.__init__(self, parent) self.index = index self.setPixmap(QPixmap(25, 25)) self.selected = False
[docs] def fill(self, color): """Fills the tile with the specified color :param color: Target color :type color: QColor """ self.color = color self.pixmap().fill(self.color)
[docs] def paintEvent(self, event): """Paint event for the color tile which draws the grid :param event: QPaintEvent event :type event: QPaintEvent """ super().paintEvent(event) painter = QPainter(self) if self.selected: pen = QPen(Qt.red) pen.setWidth(5) else: pen = QPen(Qt.black) pen.setWidth(1) painter.setPen(pen) painter.setBrush(Qt.NoBrush) painter.drawRect(0, 0, 24, 24)
[docs] def mousePressEvent(self, event): """Handles clicking on the color tile to select it :param event: Source event :type event: QMouseEvent """ self.clicked.emit(self.color) self.select()
[docs] def select(self): """Handles selecting the color tile """ self.selected = True self.color_selected.emit(self.index) self.update()
[docs] def deselect(self): """Handles de-selecting the color tile """ self.selected = False self.update()
[docs]class ColorPalette(QFrame): """Represents the entire grid of color tiles in the color picker """ def __init__(self): super().__init__() self.grid = QGridLayout() self.grid.setSpacing(0) self.grid.setContentsMargins(0, 0, 0, 0) self.setLayout(self.grid) self.palette = [Color(n) for n in range(256)] self.setFrameShape(QFrame.Panel) self.setFrameShadow(QFrame.Sunken) self.setLineWidth(3) self.setFixedSize( 400 + self.lineWidth() * 2, 400 + self.lineWidth() * 2 ) positions = [(row, col) for row in range(16) for col in range(16)] colors = [ (red, green, blue) for blue in range(4) for red in range(8) for green in range(8) ] for position, color, swatch in zip(positions, colors, self.palette): color = QColor(*upsample(*color)) swatch.fill(color) swatch.color_selected.connect(self.selectColor) self.grid.addWidget(swatch, *position) self.enabled = False pyqtSlot(int)
[docs] def selectColor(self, index): """Selects a color in the color picker :param index: Index of the selected color :type index: int """ for idx in range(self.grid.count()): if idx != index: self.grid.itemAt(idx).widget().deselect()
[docs]class ColorValidator(QValidator): """Provides input field validation to color picker RGB values :param top: RGB value ceiling :type top: int :param parent: Parent widget, defaults to None :type parent: QWidget, optional """ def __init__(self, top, parent=None): super().__init__(parent) self.top = top
[docs] def validate(self, value, pos): """Validates an RGB input :param value: Value of the RGB input :type value: int :param pos: Position of the cursor in the input field :type pos: int :return: Result of the validation :rtype: QValidator """ if value != "": try: int(value) except ValueError: return (QValidator.Invalid, value, pos) else: return (QValidator.Acceptable, value, pos) if 0 <= int(value) <= self.top: return (QValidator.Acceptable, value, pos) else: return (QValidator.Invalid, value, pos)
[docs]class ColorPicker(QDialog): """Represents the dialog containing the color picker :param parent: Parent widget, defaults to None :type parent: QWidget, optional """ preview_color = pyqtSignal(QColor) def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Select Color") actions = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) actions.accepted.connect(self.accept) actions.rejected.connect(self.reject) self.color = QColor() dialog_layout = QVBoxLayout() main_layout = QHBoxLayout() value_layout = QVBoxLayout() eight_bit_form = QFormLayout() fullcolor_form = QFormLayout() self.preview = QLabel() self.preview_pixmap = QPixmap(100, 50) self.preview.setFrameShape(QFrame.Panel) self.preview.setFrameShadow(QFrame.Sunken) self.preview.setLineWidth(3) self.preview.setScaledContents(True) value_layout.addWidget(self.preview) header = QFont() header.setBold(True) ebframe = QFrame() ebframe.setFrameShape(QFrame.StyledPanel) eight_bit_form.addRow(QLabel("8-bit color")) eight_bit_form.itemAt(0).setAlignment(Qt.AlignCenter) eight_bit_form.itemAt(0).widget().setFont(header) self.r8 = QLineEdit() self.r8.setFixedWidth(60) self.r8.setValidator(ColorValidator(7)) self.r8.editingFinished.connect(self.set8BitColors) self.g8 = QLineEdit() self.g8.setFixedWidth(60) self.g8.setValidator(ColorValidator(7)) self.g8.editingFinished.connect(self.set8BitColors) self.b8 = QLineEdit() self.b8.setFixedWidth(60) self.b8.setValidator(ColorValidator(3)) self.b8.editingFinished.connect(self.set8BitColors) self.hex8 = QLineEdit() self.hex8.setValidator( QRegExpValidator(QRegExp(r"#?(?:[0-9a-fA-F]{2})")) ) self.hex8.setFixedWidth(60) self.hex8.editingFinished.connect(self.set8BitHex) self.hex8.installEventFilter(self) eight_bit_form.addRow(QLabel("Red:"), self.r8) eight_bit_form.addRow(QLabel("Green:"), self.g8) eight_bit_form.addRow(QLabel("Blue:"), self.b8) eight_bit_form.addRow(QLabel("Hex:"), self.hex8) ebframe.setLayout(eight_bit_form) value_layout.addWidget(ebframe) fcframe = QFrame() fcframe.setFrameShape(QFrame.StyledPanel) fullcolor_form.addRow(QLabel("Full color")) fullcolor_form.itemAt(0).setAlignment(Qt.AlignCenter) fullcolor_form.itemAt(0).widget().setFont(header) self.r24 = QLineEdit() self.r24.setFixedWidth(60) self.r24.setValidator(ColorValidator(255)) self.r24.editingFinished.connect(self.set24BitColors) self.g24 = QLineEdit() self.g24.setValidator(ColorValidator(255)) self.g24.setFixedWidth(60) self.g24.editingFinished.connect(self.set24BitColors) self.b24 = QLineEdit() self.b24.setValidator(ColorValidator(255)) self.b24.setFixedWidth(60) self.b24.editingFinished.connect(self.set24BitColors) self.hex24 = QLineEdit() self.hex24.setValidator( QRegExpValidator(QRegExp(r"#?(?:[0-9a-fA-F]{6})")) ) self.hex24.setFixedWidth(60) self.hex24.installEventFilter(self) fullcolor_form.addRow(QLabel("Red:"), self.r24) fullcolor_form.addRow(QLabel("Green:"), self.g24) fullcolor_form.addRow(QLabel("Blue:"), self.b24) fullcolor_form.addRow(QLabel("Hex:"), self.hex24) fcframe.setLayout(fullcolor_form) value_layout.addWidget(fcframe) self.color_palette = ColorPalette() for swatch in self.color_palette.palette: swatch.clicked.connect(self.setColor) main_layout.addWidget(self.color_palette) main_layout.addLayout(value_layout) dialog_layout.setSizeConstraint(QLayout.SetFixedSize) dialog_layout.addLayout(main_layout) dialog_layout.addWidget(actions) self.setLayout(dialog_layout)
[docs] def eventFilter(self, source, event): """Event filter to handle the dialog losing focus :param source: Event source :type source: QObject :param event: Source event :type event: QEvent :return: Whether to handle the event further down :rtype: bool """ if event.type() == QEvent.FocusOut: if source is self.hex8: self.set8BitHex() else: self.set24BitHex() return False
[docs] def getColor(self): """Gets the chosen color from the color picker :return: Chosen color :rtype: QColor """ return self.color
[docs] @pyqtSlot(QColor) def setColor(self, value): """Sets the chosen color of the color picker :param value: The color to be set :type value: QColor """ self.color = QColor( *normalize(value.red(), value.green(), value.blue()) ) self.preview_color.emit(self.color) self.preview_pixmap.fill(self.color) self.preview.setPixmap(self.preview_pixmap) self.cur_r24 = self.color.red() self.cur_g24 = self.color.green() self.cur_b24 = self.color.blue() self.cur_hex24 = self.color.name().upper() self.r24.setText(str(self.cur_r24)) self.g24.setText(str(self.cur_g24)) self.b24.setText(str(self.cur_b24)) self.hex24.setText(self.cur_hex24) for swatch in self.color_palette.palette: if swatch.color == self.color: swatch.select() self.cur_r8, self.cur_g8, self.cur_b8 = downsample( self.cur_r24, self.cur_g24, self.cur_b24 ) self.cur_hex8 = format( (self.cur_r8 << 5) | (self.cur_g8 << 2) | self.cur_b8, "X" ).zfill(2) self.r8.setText(str(self.cur_r8)) self.g8.setText(str(self.cur_g8)) self.b8.setText(str(self.cur_b8)) self.hex8.setText("#" + self.cur_hex8)
[docs] def set8BitColors(self): """Sets the active color to that chosen by the 8-bit input fields """ r = self.cur_r8 if self.r8.text() == "" else int(self.r8.text()) g = self.cur_g8 if self.g8.text() == "" else int(self.g8.text()) b = self.cur_b8 if self.b8.text() == "" else int(self.b8.text()) self.setColor(QColor(*upsample(r, g, b)))
[docs] def set24BitColors(self): """Sets the active color to that chosen by the 24-bit input fields """ r = self.cur_r24 if self.r24.text() == "" else int(self.r24.text()) g = self.cur_g24 if self.g24.text() == "" else int(self.g24.text()) b = self.cur_b24 if self.b24.text() == "" else int(self.b24.text()) self.setColor(QColor(r, g, b))
[docs] def set8BitHex(self): """Sets the active color to that chosen by the 8-bit hex input field """ hex = self.hex8.text().lstrip("#") hex = int(self.cur_hex8, 16) if hex == "" else int(hex, 16) r = (hex >> 5) & 7 g = (hex >> 2) & 7 b = hex & 3 self.setColor(QColor(*upsample(r, g, b)))
[docs] def set24BitHex(self): """Sets the active color to that chosen by the 24-bit hex input field """ hex = self.hex24.text().lstrip("#") hex = self.cur_hex24 if hex == "" else "#" + hex.zfill(6) self.setColor(QColor(hex))