Commit 4766d995 authored by phob1an's avatar phob1an 🎧
Browse files

fork

parents
.*.swp
*.pyc
ui_*.py
build
CMakeLists.txt.user
project(colorpick)
cmake_minimum_required(VERSION 3.0)
find_package(Qt5 CONFIG REQUIRED Core Widgets)
find_package(KF5GuiAddons CONFIG REQUIRED)
find_package(KF5WidgetsAddons CONFIG REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Woverloaded-virtual -std=c++11")
add_subdirectory(src)
add_subdirectory(desktop)
Copyright (c) 2012-2014 Aurélien Gâteau and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the
disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* The name of the contributors may not be used to endorse or
promote products derived from this software without specific prior
written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Colorpick: Color picker and contrast checker
![Contrast check](screenshots/contrast.png)
Colorpick is a color picker, which makes it easy to check text readability by
letting you pick a background and foreground color and computing the contrast
between them.
It lets you know if contrast is good enough according to
<http://www.w3.org/TR/WCAG20/#visual-audio-contrast>.
It also comes with a handy magnified-picker, which you can control with the
cursor keys, for precise picking.
Finally, it lets you adjust colors and copy them to the clipboard as different
formats.
Colorpick is managed using the [lightweight project management policy][1].
[1]: http://agateau.com/2014/lightweight-project-management
## Requirements
- CMake
- Qt 5
- KF5GuiAddons
- KF5WidgetsAddons
## Installation
Create a build directory and change to it:
mkdir build
cd build
Run CMake and build:
cmake path/to/colorpick
make
Install (must be run as root if installing to /usr or /usr/local):
make install
## Author
Aurélien Gâteau
## License
BSD
install(FILES colorpick.desktop DESTINATION share/applications)
[Desktop Entry]
Type=Application
Version=1.0
Name=Colorpick
GenericName=A color picker
Icon=fill-color
TryExec=colorpick
Exec=colorpick
Categories=Qt;KDE;Graphics;Utility;
# Sources
set(colorpick_SRCS
colorspace.cpp
coloreditor.cpp
colorpicker.cpp
contrastpreview.cpp
hsvcolorspace.cpp
imagegradientselector.cpp
main.cpp
rgbcolorspace.cpp
componenteditor.cpp
window.cpp
)
# Building
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
add_executable(colorpick ${colorpick_SRCS})
target_link_libraries(colorpick Qt5::Core Qt5::Widgets
KF5::GuiAddons
KF5::WidgetsAddons)
# Install
install(TARGETS colorpick
DESTINATION bin
)
#include "coloreditor.h"
#include "colorpicker.h"
#include "hsvcolorspace.h"
#include "rgbcolorspace.h"
#include "componenteditor.h"
#include <KColorButton>
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QLocale>
#include <QMargins>
#include <QMenu>
#include <QMimeData>
#include <QPainter>
#include <QPushButton>
#include <QToolButton>
#include <QWidgetAction>
/**
* A QToolButton which has no border and immediatly shows a menu (there is no way to alter the popup mode of the button
* created when adding an action directly to QLineEdit)
*/
class MenuLineEditButton : public QToolButton
{
public:
MenuLineEditButton(QMenu* menu)
{
setMenu(menu);
setPopupMode(QToolButton::InstantPopup);
setCursor(Qt::ArrowCursor);
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
QIcon::Mode state = isDown() ? QIcon::Selected : QIcon::Normal;
QPixmap pix = icon().pixmap(size(), state, QIcon::Off);
QRect pixRect = QRect(QPoint(0, 0), pix.size() / pix.devicePixelRatio());
pixRect.moveCenter(rect().center());
painter.drawPixmap(pixRect, pix);
}
};
ColorEditor::ColorEditor(const QIcon &icon, QWidget *parent) : QWidget(parent)
{
setAcceptDrops(true);
QLabel *iconLabel = new QLabel;
iconLabel->setPixmap(icon.pixmap(40, 40));
mColorButton = new KColorButton();
mColorButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
connect(mColorButton, &KColorButton::changed, this, &ColorEditor::setColor);
mLineEdit = new QLineEdit();
connect(mLineEdit, &QLineEdit::textEdited, this, [this](const QString &text) {
if (QColor::isValidColor(text)) {
mFromLineEdit = true;
setColor(QColor(text));
mFromLineEdit = false;
}
});
QToolButton *pickerButton = new QToolButton();
pickerButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
pickerButton->setIcon(QIcon::fromTheme("color-picker"));
connect(pickerButton, &QToolButton::clicked, this, &ColorEditor::startPicking);
setupCopyButton();
mRgbEditor = new ComponentEditor(RgbColorSpace::instance());
connect(mRgbEditor, &ComponentEditor::colorChanged, this, &ColorEditor::setColor);
mHsvEditor = new ComponentEditor(HsvColorSpace::instance());
connect(mHsvEditor, &ComponentEditor::colorChanged, this, &ColorEditor::setColor);
QGridLayout *layout = new QGridLayout(this);
layout->addWidget(iconLabel, 0, 0, 2, 1, Qt::AlignTop);
layout->addWidget(mColorButton, 0, 1);
layout->addWidget(mLineEdit, 0, 2);
layout->addWidget(pickerButton, 0, 3);
QBoxLayout *componentEditorLayout = new QVBoxLayout;
componentEditorLayout->setContentsMargins(QMargins());
componentEditorLayout->addWidget(mRgbEditor);
componentEditorLayout->addWidget(mHsvEditor);
layout->addLayout(componentEditorLayout, 1, 1, 1, 3);
}
void ColorEditor::setupCopyButton()
{
mCopyMenu = new QMenu(this);
connect(mCopyMenu, &QMenu::aboutToShow, this, &ColorEditor::fillCopyMenu);
MenuLineEditButton *copyButton = new MenuLineEditButton(mCopyMenu);
copyButton->setIcon(QIcon::fromTheme("edit-copy"));
QWidgetAction *copyAction = new QWidgetAction(this);
copyAction->setDefaultWidget(copyButton);
mLineEdit->addAction(copyAction, QLineEdit::TrailingPosition);
}
QColor ColorEditor::color() const
{
return mColor;
}
void ColorEditor::setColor(const QColor &color)
{
if (mColor != color) {
mColor = color;
updateFromColor();
colorChanged(mColor);
}
}
void ColorEditor::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasColor()) {
event->acceptProposedAction();
}
}
void ColorEditor::dropEvent(QDropEvent *event)
{
QColor color = qvariant_cast<QColor>(event->mimeData()->colorData());
setColor(color);
}
void ColorEditor::updateFromColor()
{
mColorButton->setColor(mColor);
if (!mFromLineEdit) {
mLineEdit->setText(mColor.name());
}
mRgbEditor->setColor(mColor);
mHsvEditor->setColor(mColor);
}
void ColorEditor::startPicking()
{
ColorPicker *picker = new ColorPicker;
connect(picker, &ColorPicker::colorChanged, this, &ColorEditor::setColor);
picker->exec();
}
void ColorEditor::fillCopyMenu()
{
mCopyMenu->clear();
int r, g, b;
qreal rf, gf, bf;
mColor.getRgb(&r, &g, &b);
mColor.getRgbF(&rf, &gf, &bf);
auto myfloat = [](qreal value) {
return QString::number(value, 'g', 3);
};
auto hex = [](int value) {
return QString::number(value, 16).rightJustified(2, '0');
};
auto addColorAction = [this](const QString &text, const QString &value) {
QString fullText = ColorEditor::tr("%1: %2").arg(text).arg(value);
QAction *action = mCopyMenu->addAction(fullText);
connect(action, &QAction::triggered, this, [value]() {
QApplication::clipboard()->setText(value);
});
};
addColorAction(tr("Inkscape"), hex(r) + hex(g) + hex(b) + hex(255));
addColorAction(tr("Hexa with #"), "#" + hex(r) + hex(g) + hex(b));
addColorAction(tr("Quoted hexa with #"), "\"#" + hex(r) + hex(g) + hex(b) + "\"");
addColorAction(tr("Float values"), QString("%1, %2, %3").arg(myfloat(rf)).arg(myfloat(gf)).arg(myfloat(bf)));
addColorAction(tr("r,g,b Text"), QString("%1,%2,%3").arg(r).arg(g).arg(b));
}
#ifndef COLOREDITOR_H
#define COLOREDITOR_H
#include <QWidget>
class ComponentEditor;
class KColorButton;
class QLabel;
class QLineEdit;
class QMenu;
class QToolButton;
class ColorEditor : public QWidget
{
Q_OBJECT
public:
explicit ColorEditor(const QIcon &icon, QWidget *parent = 0);
QColor color() const;
void setColor(const QColor &color);
Q_SIGNALS:
void colorChanged(const QColor &color);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
void updateFromColor();
void startPicking();
void fillCopyMenu();
KColorButton *mColorButton;
QLineEdit *mLineEdit;
QMenu *mCopyMenu;
ComponentEditor *mRgbEditor;
ComponentEditor *mHsvEditor;
QColor mColor;
bool mFromLineEdit = false;
void setupCopyButton();
};
#endif // COLOREDITOR_H
#include "colorpicker.h"
#include <QApplication>
#include <QCursor>
#include <QDebug>
#include <QDesktopWidget>
#include <QImage>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPaintEvent>
#include <QScreen>
#include <QShowEvent>
#include <QTimer>
static constexpr int GRAB_RADIUS = 16;
static constexpr int GRAB_SIZE = GRAB_RADIUS * 2 + 1;
static constexpr int MAGNIFY = 4;
ColorPicker::ColorPicker()
{
setWindowFlags(Qt::Window | Qt::X11BypassWindowManagerHint);
setAttribute(Qt::WA_DeleteOnClose);
setFixedSize(GRAB_SIZE * MAGNIFY, GRAB_SIZE * MAGNIFY);
mTimer = new QTimer(this);
mTimer->setInterval(10);
connect(mTimer, &QTimer::timeout, this, &ColorPicker::updatePosition);
}
ColorPicker::~ColorPicker()
{
releaseMouse();
releaseKeyboard();
}
void ColorPicker::showEvent(QShowEvent *)
{
updatePosition();
mTimer->start();
QTimer::singleShot(100, this, [this]() {
grabKeyboard();
grabMouse(Qt::CrossCursor);
});
}
void ColorPicker::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emitColorChanged();
close();
}
}
void ColorPicker::keyPressEvent(QKeyEvent *event)
{
int key = event->key();
int dx = 0;
int dy = 0;
if (key == Qt::Key_Left) {
dx = -1;
} else if (key == Qt::Key_Right) {
dx = 1;
} else if (key == Qt::Key_Up) {
dy = -1;
} else if (key == Qt::Key_Down) {
dy = 1;
} else if (key == Qt::Key_Return) {
emitColorChanged();
close();
return;
} else if (key == Qt::Key_Escape) {
close();
return;
} else {
return;
}
QPoint pos = QCursor::pos();
QCursor::setPos(pos.x() + dx, pos.y() + dy);
}
void ColorPicker::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPixmap pix = mPixmap.scaled(GRAB_SIZE * MAGNIFY, GRAB_SIZE * MAGNIFY);
painter.drawPixmap(0, 0, pix);
painter.setPen(Qt::darkGray);
QRect rct = rect().adjusted(0, 0, -1, -1);
painter.drawRect(rct);
painter.setPen(Qt::white);
rct = rct.adjusted(1, 1, -1, -1);
painter.drawRect(rct);
painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
painter.setPen(Qt::white);
painter.drawRect(GRAB_RADIUS * MAGNIFY - 1, GRAB_RADIUS * MAGNIFY - 1, MAGNIFY + 1, MAGNIFY + 1);
}
void ColorPicker::emitColorChanged()
{
QImage image = mPixmap.toImage();
QColor color = QColor(image.pixel(GRAB_RADIUS, GRAB_RADIUS));
colorChanged(color);
}
static QScreen *findScreenAt(const QPoint &pos)
{
for (QScreen *screen : QGuiApplication::screens()) {
if (screen->geometry().contains(pos)) {
return screen;
}
}
return nullptr;
}
void ColorPicker::updatePosition()
{
QPoint pos = QCursor::pos();
QScreen *screen = findScreenAt(pos);
if (!screen) {
qWarning() << "Could not find a screen containing" << pos;
return;
}
QRect desktopRect = screen->geometry();
QPoint newPos;
if (pos.x() + GRAB_SIZE + width() < desktopRect.width()) {
newPos.setX(pos.x() + GRAB_SIZE);
} else {
newPos.setX(pos.x() - GRAB_SIZE - width());
}
if (pos.y() + GRAB_SIZE + height() < desktopRect.height()) {
newPos.setY(pos.y() + GRAB_SIZE);
} else {
newPos.setY(pos.y() - GRAB_SIZE - height());
}
move(newPos);
WId wid = QApplication::desktop()->winId();
mPixmap = screen->grabWindow(wid, pos.x() - GRAB_SIZE / 2, pos.y() - GRAB_SIZE / 2, GRAB_SIZE, GRAB_SIZE);
update();
}
#ifndef COLORPICKER_H
#define COLORPICKER_H
#include <QDialog>
class QTimer;
class ColorPicker : public QDialog
{
Q_OBJECT
public:
ColorPicker();
~ColorPicker();
Q_SIGNALS:
void colorChanged(const QColor &);
protected:
void showEvent(QShowEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
void updatePosition();
void emitColorChanged();
QTimer *mTimer;
QPixmap mPixmap;
};
#endif // COLORPICKER_H
#include "colorspace.h"
ColorSpace::~ColorSpace()
{
}
#ifndef COLORSPACE_H
#define COLORSPACE_H
#include <QColor>
#include <QString>
#include <QVector>
/**
* Represents a color space (RGB, HSV...)
*
* Provides a uniform API to manipulate colors independently of the actual color space.
* Implmentations should be lightweight objects since they have no state.
*/
class ColorSpace
{
public:
virtual ~ColorSpace();
virtual QString name(int idx) const = 0;
virtual int value(const QColor &color, int idx) const = 0;
virtual int maximum(int idx) const = 0;
virtual QVector<int> values(const QColor &color) const = 0;
virtual QColor fromValues(const QVector<int> &values) const = 0;
};
#endif // COLORSPACE_H
#include "componenteditor.h"
#include "colorspace.h"
#include "imagegradientselector.h"
#include <QGridLayout>
#include <QImage>
#include <QLabel>
#include <QSpinBox>
ComponentEditor::ComponentEditor(ColorSpace *colorSpace, QWidget *parent)
: QWidget(parent)
, mColorSpace(colorSpace)
{
QGridLayout *layout = new QGridLayout(this);
layout->setContentsMargins(QMargins());
layout->setSpacing(0);
</