mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 07:14:13 +02:00
Revert "Draw the folio with inverted lightness on a dark palette"
This commit is contained in:
@@ -248,8 +248,6 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/qet.h
|
||||
${QET_DIR}/sources/qeticons.cpp
|
||||
${QET_DIR}/sources/qeticons.h
|
||||
${QET_DIR}/sources/palettegraphicsview.cpp
|
||||
${QET_DIR}/sources/palettegraphicsview.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/sources/qetstyle.cpp
|
||||
|
||||
+2
-4
@@ -26,7 +26,6 @@
|
||||
#include "diagramposition.h"
|
||||
#include "factory/elementfactory.h"
|
||||
#include "qetapp.h"
|
||||
#include "qetpalette.h"
|
||||
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "qetgraphicsitem/conductortextitem.h"
|
||||
@@ -284,11 +283,10 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
|
||||
* if background color is black,
|
||||
* then grid spots shall be white,
|
||||
* else they shall be black in color.
|
||||
* A view that shows the sheet with its lightness inverted
|
||||
* gets softer dots, see QET::Palette::gridDotColor.
|
||||
*/
|
||||
QPen pen;
|
||||
pen.setColor(QET::Palette::gridDotColor(Diagram::background_color, m_inverted_lightness));
|
||||
Diagram::background_color == Qt::black? pen.setColor(Qt::white)
|
||||
: pen.setColor(Qt::black);
|
||||
pen.setCosmetic(true);
|
||||
p->setPen(pen);
|
||||
|
||||
|
||||
@@ -123,7 +123,6 @@ class Diagram : public QGraphicsScene
|
||||
qreal diagram_qet_version_;
|
||||
|
||||
bool draw_grid_;
|
||||
bool m_inverted_lightness = false;
|
||||
bool use_border_;
|
||||
bool draw_guides_;
|
||||
QList<Diagram::Guide> m_guides_list;
|
||||
@@ -223,7 +222,6 @@ class Diagram : public QGraphicsScene
|
||||
ExportProperties applyProperties(const ExportProperties &);
|
||||
void setDisplayGrid(bool);
|
||||
bool displayGrid();
|
||||
void setInvertedLightness(bool);
|
||||
void setDisplayGuides(bool);
|
||||
bool displayGuides();
|
||||
void updateProjectGuides(const QList<GuideProperties> &guides);
|
||||
@@ -357,18 +355,6 @@ inline void Diagram::setDisplayGrid(bool dg) {
|
||||
draw_grid_ = dg;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::setInvertedLightness
|
||||
Tell the diagram whether the view painting it will show the result
|
||||
with its lightness inverted (PaletteGraphicsView on a dark palette).
|
||||
drawBackground draws a softer grid in that case. Printing
|
||||
and export never set this.
|
||||
@param inverted
|
||||
*/
|
||||
inline void Diagram::setInvertedLightness(bool inverted) {
|
||||
m_inverted_lightness = inverted;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::displayGrid
|
||||
@return draw_grid_ true if the grid is drawn, false otherwise.
|
||||
|
||||
+2
-14
@@ -39,9 +39,7 @@
|
||||
#include "ElementsCollection/xmlelementcollection.h"
|
||||
#include "NameList/nameslist.h"
|
||||
#include "elementdialog.h"
|
||||
#include <QApplication>
|
||||
#include <QDropEvent>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
|
||||
/**
|
||||
@@ -50,7 +48,7 @@
|
||||
@param parent Le QWidget parent de cette vue de schema
|
||||
*/
|
||||
DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
||||
PaletteGraphicsView (parent),
|
||||
QGraphicsView (parent),
|
||||
m_diagram (diagram)
|
||||
{
|
||||
grabGesture(Qt::PinchGesture);
|
||||
@@ -1083,16 +1081,6 @@ bool DiagramView::event(QEvent *e) {
|
||||
return(QGraphicsView::event(e));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::paintingInverted
|
||||
Reimplemented from PaletteGraphicsView: tell the diagram it is being
|
||||
drawn for an inverted display, so it softens its grid.
|
||||
*/
|
||||
void DiagramView::paintingInverted(bool inverted)
|
||||
{
|
||||
m_diagram->setInvertedLightness(inverted);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::paintEvent
|
||||
Reimplemented from QGraphicsView
|
||||
@@ -1100,7 +1088,7 @@ void DiagramView::paintingInverted(bool inverted)
|
||||
*/
|
||||
void DiagramView::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
PaletteGraphicsView::paintEvent(event);
|
||||
QGraphicsView::paintEvent(event);
|
||||
|
||||
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "titleblock/templatelocation.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include "palettegraphicsview.h"
|
||||
#include <QGraphicsView>
|
||||
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
@@ -35,7 +35,7 @@ class QGestureEvent;
|
||||
This class provides a widget to render an electric diagram in an editable,
|
||||
interactive way.
|
||||
*/
|
||||
class DiagramView : public PaletteGraphicsView
|
||||
class DiagramView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -84,7 +84,6 @@ class DiagramView : public PaletteGraphicsView
|
||||
///Set for one call only, by the Escape handler, to let focus leave the view.
|
||||
bool m_releasing_focus = false;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void paintingInverted(bool inverted) override;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
void mouseReleaseEvent(QMouseEvent *) override;
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "palettegraphicsview.h"
|
||||
|
||||
#include "qetpalette.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QStyleHintReturnMask>
|
||||
#include <QStyleOptionRubberBand>
|
||||
#include <QtMath>
|
||||
|
||||
PaletteGraphicsView::PaletteGraphicsView(QWidget *parent) :
|
||||
QGraphicsView(parent)
|
||||
{
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
|
||||
PaletteGraphicsView::PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent) :
|
||||
QGraphicsView(scene, parent)
|
||||
{
|
||||
qApp->installEventFilter(this);
|
||||
listenToScene(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::setScene
|
||||
Same as QGraphicsView::setScene, and keeps the scene's updates flowing
|
||||
(see the class description).
|
||||
*/
|
||||
void PaletteGraphicsView::setScene(QGraphicsScene *scene)
|
||||
{
|
||||
QGraphicsView::setScene(scene);
|
||||
listenToScene(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::invertsLightness
|
||||
@return true when the scene is shown with its lightness inverted,
|
||||
i.e. when the application palette is dark. The application palette,
|
||||
not the view's own: a style sheet on an ancestor (the folio tab
|
||||
widget has one) makes QStyleSheetStyle pin the palette of every
|
||||
widget under it to the application palette in force when the sheet
|
||||
was applied, so after a live light/dark switch palette() is stale.
|
||||
*/
|
||||
bool PaletteGraphicsView::invertsLightness() const
|
||||
{
|
||||
return QET::Palette::isDark(QApplication::palette());
|
||||
}
|
||||
|
||||
void PaletteGraphicsView::paintingInverted(bool inverted)
|
||||
{
|
||||
Q_UNUSED(inverted)
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::listenToScene
|
||||
Connect a receiver to the scene's changed() signal, dropping the one
|
||||
on the previous scene. Any receiver does; this one has nothing to do.
|
||||
The connection dies with the view.
|
||||
*/
|
||||
void PaletteGraphicsView::listenToScene(QGraphicsScene *scene)
|
||||
{
|
||||
disconnect(m_scene_connection);
|
||||
m_scene_connection = QMetaObject::Connection();
|
||||
if (scene)
|
||||
m_scene_connection = connect(scene, &QGraphicsScene::changed,
|
||||
this, [](const QList<QRectF> &) {});
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::eventFilter
|
||||
Repaint the whole viewport when the application palette changes. Qt
|
||||
sends that change to the application object and then repaints only
|
||||
the widgets whose own palette changed with it, which under a style
|
||||
sheet is not the case (see invertsLightness()): the scene would then
|
||||
repaint only what it updates itself, and the viewport around the
|
||||
sheet would keep the colors of the previous palette. The filter sits
|
||||
on the application object, the one receiver Qt always notifies.
|
||||
*/
|
||||
bool PaletteGraphicsView::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == qApp && event->type() == QEvent::ApplicationPaletteChange)
|
||||
viewport()->update();
|
||||
return QGraphicsView::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::paintEvent
|
||||
Paints as QGraphicsView on a light palette, inverted on a dark one.
|
||||
*/
|
||||
void PaletteGraphicsView::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
if (invertsLightness())
|
||||
paintInverted(event->rect());
|
||||
else
|
||||
QGraphicsView::paintEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::paintInverted
|
||||
Render \a area of the viewport into an off-screen image, invert the
|
||||
lightness of that image between the palette's Base and Text colors and
|
||||
blit it to the viewport. Inverting the finished rendering turns the
|
||||
white sheet dark and the black ink light in one pass, and keeps the
|
||||
hue of colored strokes.
|
||||
@param area the part of the viewport to repaint, in viewport coordinates
|
||||
*/
|
||||
void PaletteGraphicsView::paintInverted(const QRect &area)
|
||||
{
|
||||
const QRect rect = area.intersected(viewport()->rect());
|
||||
if (rect.isEmpty())
|
||||
return;
|
||||
|
||||
const qreal ratio = viewport()->devicePixelRatioF();
|
||||
QImage buffer(qCeil(rect.width() * ratio), qCeil(rect.height() * ratio),
|
||||
QImage::Format_RGB32);
|
||||
buffer.setDevicePixelRatio(ratio);
|
||||
// render() paints only what the scene draws; what it leaves blank is
|
||||
// the white sheet, which the inversion turns into the Base color.
|
||||
buffer.fill(Qt::white);
|
||||
|
||||
QPainter buffer_painter(&buffer);
|
||||
buffer_painter.setRenderHints(renderHints());
|
||||
paintingInverted(true);
|
||||
render(&buffer_painter, QRectF(QPointF(0, 0), QSizeF(rect.size())),
|
||||
rect, Qt::IgnoreAspectRatio);
|
||||
paintingInverted(false);
|
||||
buffer_painter.end();
|
||||
|
||||
// The application palette, for the reason given in invertsLightness().
|
||||
const QPalette application_palette = QApplication::palette();
|
||||
QET::Palette::invertLightness(buffer, application_palette.color(QPalette::Base),
|
||||
application_palette.color(QPalette::Text));
|
||||
|
||||
QPainter painter(viewport());
|
||||
painter.drawImage(rect.topLeft(), buffer);
|
||||
drawRubberBand(painter);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief PaletteGraphicsView::drawRubberBand
|
||||
Draw the selection rubber band the way QGraphicsView::paintEvent does.
|
||||
Rendering the view into an off-screen image skips it, so it is drawn
|
||||
here instead, after the inversion, in the palette colors.
|
||||
@param painter a painter on the viewport
|
||||
*/
|
||||
void PaletteGraphicsView::drawRubberBand(QPainter &painter)
|
||||
{
|
||||
const QRect band = rubberBandRect();
|
||||
if (band.isNull())
|
||||
return;
|
||||
|
||||
QStyleOptionRubberBand option;
|
||||
option.initFrom(viewport());
|
||||
option.rect = band;
|
||||
option.shape = QRubberBand::Rectangle;
|
||||
|
||||
QStyleHintReturnMask mask;
|
||||
if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option,
|
||||
viewport(), &mask))
|
||||
painter.setClipRegion(mask.region, Qt::IntersectClip);
|
||||
viewport()->style()->drawControl(QStyle::CE_RubberBand, &option,
|
||||
&painter, viewport());
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef PALETTE_GRAPHICS_VIEW_H
|
||||
#define PALETTE_GRAPHICS_VIEW_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
|
||||
class QPainter;
|
||||
|
||||
/**
|
||||
A QGraphicsView that shows its scene with inverted lightness while its
|
||||
palette is dark: white becomes the palette's Base color, black its Text
|
||||
color, and colored strokes keep their hue. The scene itself is left as
|
||||
drawn, so printing and exporting it still give black on white. On a
|
||||
light palette the view paints exactly as QGraphicsView does.
|
||||
|
||||
The view paints through QGraphicsView::render() into an image and blits
|
||||
the inverted image. QGraphicsView delivers scene updates straight to
|
||||
its viewport when nobody listens to QGraphicsScene::changed(), and in
|
||||
that mode the scene clears its "update everything" flag only when the
|
||||
items are painted straight onto the viewport, which never happens
|
||||
here: from the second QGraphicsScene::update() on, every scene update
|
||||
and every item update would wait for an unrelated repaint. So the view
|
||||
listens to changed() on every scene it is given, which makes the scene
|
||||
clear the flag before it emits. Set the scene through this class, not
|
||||
through a QGraphicsView pointer.
|
||||
*/
|
||||
class PaletteGraphicsView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PaletteGraphicsView(QWidget *parent = nullptr);
|
||||
explicit PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
|
||||
|
||||
void setScene(QGraphicsScene *scene);
|
||||
bool invertsLightness() const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
/**
|
||||
Called with true right before the scene is rendered for an
|
||||
inverted display and with false right after, so a scene can
|
||||
adapt what it draws (a softer grid, for instance). Does
|
||||
nothing by default.
|
||||
*/
|
||||
virtual void paintingInverted(bool inverted);
|
||||
|
||||
private:
|
||||
void listenToScene(QGraphicsScene *scene);
|
||||
void paintInverted(const QRect &area);
|
||||
void drawRubberBand(QPainter &painter);
|
||||
|
||||
QMetaObject::Connection m_scene_connection;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -18,9 +18,7 @@
|
||||
#include "qetpalette.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QStyle>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
@@ -77,57 +75,6 @@ bool QET::Palette::isDark(const QPalette &palette)
|
||||
return palette.color(QPalette::Active, QPalette::Window).lightness() < 128;
|
||||
}
|
||||
|
||||
void QET::Palette::invertLightness(QImage &image, const QColor &sheet,
|
||||
const QColor &ink)
|
||||
{
|
||||
if (image.format() != QImage::Format_RGB32)
|
||||
image.convertTo(QImage::Format_RGB32);
|
||||
|
||||
// One table per channel maps the inverted value (0 = was white,
|
||||
// 255 = was black) onto the sheet..ink span.
|
||||
uchar red_of[256], green_of[256], blue_of[256];
|
||||
for (int v = 0; v < 256; ++v) {
|
||||
red_of[v] = uchar(sheet.red() + (ink.red() - sheet.red()) * v / 255);
|
||||
green_of[v] = uchar(sheet.green() + (ink.green() - sheet.green()) * v / 255);
|
||||
blue_of[v] = uchar(sheet.blue() + (ink.blue() - sheet.blue()) * v / 255);
|
||||
}
|
||||
|
||||
/* Inverting the lightness of an HSL color while keeping its hue and
|
||||
saturation leaves the distance between the highest and the lowest
|
||||
channel unchanged, so it comes down to one offset per pixel:
|
||||
c + 255 - max - min. The offset turns the highest channel into
|
||||
255 - min and the lowest into 255 - max, so no channel can leave
|
||||
the 0..255 range and no clamping is needed. The loop runs on every
|
||||
repaint of a folio, hence the plain integer arithmetic. */
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
quint32 *line = reinterpret_cast<quint32 *>(image.scanLine(y));
|
||||
for (int x = 0, width = image.width(); x < width; ++x) {
|
||||
const quint32 pixel = line[x];
|
||||
const int red = (pixel >> 16) & 0xff;
|
||||
const int green = (pixel >> 8) & 0xff;
|
||||
const int blue = pixel & 0xff;
|
||||
int highest = red > green ? red : green;
|
||||
int lowest = red < green ? red : green;
|
||||
if (blue > highest) highest = blue;
|
||||
if (blue < lowest) lowest = blue;
|
||||
const int offset = 255 - highest - lowest;
|
||||
line[x] = 0xff000000u
|
||||
| (quint32(red_of[red + offset]) << 16)
|
||||
| (quint32(green_of[green + offset]) << 8)
|
||||
| quint32(blue_of[blue + offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QColor QET::Palette::gridDotColor(const QColor &sheet, bool inverted)
|
||||
{
|
||||
if (sheet == QColor(Qt::black))
|
||||
return Qt::white;
|
||||
if (inverted)
|
||||
return QColor(sheet.red() * 2 / 3, sheet.green() * 2 / 3, sheet.blue() * 2 / 3);
|
||||
return Qt::black;
|
||||
}
|
||||
|
||||
double QET::Palette::contrastRatio(const QColor &a, const QColor &b)
|
||||
{
|
||||
double lighter = relativeLuminance(a);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <QPalette>
|
||||
#include <QPixmap>
|
||||
|
||||
class QImage;
|
||||
class QStyle;
|
||||
|
||||
/**
|
||||
@@ -55,28 +54,6 @@ namespace QET {
|
||||
*/
|
||||
bool isDark(const QPalette &palette);
|
||||
|
||||
/**
|
||||
Invert the lightness of every pixel of \a image, keeping its hue
|
||||
and saturation, then stretch the result between two colors: pure
|
||||
white becomes \a sheet, pure black becomes \a ink, and a red
|
||||
line stays red, only lighter. Made for a rendering of a white
|
||||
sheet that has to read on a dark palette, with sheet = Base and
|
||||
ink = Text. The image must be opaque; an image in another format
|
||||
is converted to RGB32 first.
|
||||
*/
|
||||
void invertLightness(QImage &image, const QColor &sheet = Qt::black,
|
||||
const QColor &ink = Qt::white);
|
||||
|
||||
/**
|
||||
The color of the grid dots on a sheet of color \a sheet: black,
|
||||
or white on a black sheet. With \a inverted the sheet is about to
|
||||
be shown with its lightness inverted (PaletteGraphicsView), where
|
||||
black dots would come out as bright as the ink; the dots are then
|
||||
a third of the way from the sheet color to black, which shows as
|
||||
a soft gray.
|
||||
*/
|
||||
QColor gridDotColor(const QColor &sheet, bool inverted);
|
||||
|
||||
/**
|
||||
WCAG 2 contrast ratio between two opaque colors, from 1 (equal)
|
||||
to 21 (black on white). Normal text needs at least 4.5, large
|
||||
|
||||
@@ -95,8 +95,6 @@ add_executable(
|
||||
tst_qetpalette
|
||||
tst_qetpalette.cpp
|
||||
inkcontrast.h
|
||||
${QET_DIR}/sources/palettegraphicsview.cpp
|
||||
${QET_DIR}/sources/palettegraphicsview.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGraphicsItem>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
@@ -32,12 +29,10 @@
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
#include <QStyleFactory>
|
||||
#include <QTabWidget>
|
||||
#include <QToolBar>
|
||||
|
||||
#include "inkcontrast.h"
|
||||
#include "ElementsCollection/elementpreviewdelegate.h"
|
||||
#include "palettegraphicsview.h"
|
||||
#include "qetpalette.h"
|
||||
|
||||
using QET::Palette::contrastRatio;
|
||||
@@ -75,15 +70,6 @@ class tst_qetpalette : public QObject
|
||||
void invertedLightnessKeepsHueAndAlpha();
|
||||
void elementPreviewReadsOnBothPalettes();
|
||||
void previewDelegateAdaptsLineArtOnly();
|
||||
void invertLightnessMapsSheetAndInk();
|
||||
void invertedViewReadsOnDarkSheet();
|
||||
void invertLightnessSpeed();
|
||||
void gridDotColorSoftensInvertedDots();
|
||||
void paletteViewFollowsThePalette();
|
||||
void paletteViewKeepsSceneUpdatesFlowing();
|
||||
void paletteViewDrawsTheRubberBand();
|
||||
void paletteViewFollowsTheApplicationUnderAStyleSheet();
|
||||
void paletteViewFillsWhatTheSceneLeavesBlank();
|
||||
|
||||
private:
|
||||
static void addPaletteRows();
|
||||
@@ -451,370 +437,6 @@ void tst_qetpalette::previewDelegateAdaptsLineArtOnly()
|
||||
QVERIFY2(blue, "the colored icon lost its color");
|
||||
}
|
||||
|
||||
/**
|
||||
The lightness inversion sends white to the sheet color and black to
|
||||
the ink color, lands a mid gray between the two, and keeps the hue of
|
||||
a colored line. Without sheet and ink it is a plain inversion.
|
||||
*/
|
||||
void tst_qetpalette::invertLightnessMapsSheetAndInk()
|
||||
{
|
||||
const QColor sheet(30, 30, 30);
|
||||
const QColor ink(220, 220, 220);
|
||||
QImage image(4, 1, QImage::Format_ARGB32);
|
||||
image.setPixelColor(0, 0, Qt::white);
|
||||
image.setPixelColor(1, 0, Qt::black);
|
||||
image.setPixelColor(2, 0, QColor(128, 128, 128));
|
||||
image.setPixelColor(3, 0, QColor(200, 0, 0));
|
||||
QImage plain = image;
|
||||
|
||||
QET::Palette::invertLightness(image, sheet, ink);
|
||||
QCOMPARE(image.format(), QImage::Format_RGB32);
|
||||
QCOMPARE(image.pixelColor(0, 0), sheet);
|
||||
QCOMPARE(image.pixelColor(1, 0), ink);
|
||||
const int middle = (sheet.red() + ink.red()) / 2;
|
||||
QVERIFY(qAbs(image.pixelColor(2, 0).red() - middle) <= 2);
|
||||
const QColor red = image.pixelColor(3, 0);
|
||||
QCOMPARE(red.hslHue(), 0);
|
||||
QVERIFY2(red.hslSaturationF() > 0.5, qPrintable(red.name()));
|
||||
QVERIFY2(red.lightness() > QColor(200, 0, 0).lightness(), qPrintable(red.name()));
|
||||
|
||||
QET::Palette::invertLightness(plain);
|
||||
QCOMPARE(plain.pixelColor(0, 0), QColor(Qt::black));
|
||||
QCOMPARE(plain.pixelColor(1, 0), QColor(Qt::white));
|
||||
QCOMPARE(plain.pixelColor(3, 0), QColor(255, 55, 55));
|
||||
}
|
||||
|
||||
/**
|
||||
A view rendered the way DiagramView does it on a dark palette: a part
|
||||
of the viewport goes into an image, which is inverted between the
|
||||
palette's Base and Text. The white sheet comes out as Base, black
|
||||
lines read at text contrast, and a blue box is still blue.
|
||||
*/
|
||||
void tst_qetpalette::invertedViewReadsOnDarkSheet()
|
||||
{
|
||||
QGraphicsScene scene(0, 0, 200, 120);
|
||||
scene.setBackgroundBrush(Qt::white);
|
||||
scene.addLine(10, 60, 190, 60, QPen(Qt::black, 2));
|
||||
scene.addRect(20, 20, 40, 20, QPen(Qt::NoPen), QBrush(QColor(30, 96, 176)));
|
||||
|
||||
QGraphicsView view(&scene);
|
||||
view.setFrameShape(QFrame::NoFrame);
|
||||
view.setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view.resize(200, 120);
|
||||
view.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
|
||||
// A part of the viewport that is not at its origin, as a partial
|
||||
// repaint after a scroll would be.
|
||||
const QRect area(10, 10, 100, 60);
|
||||
QImage buffer(area.size(), QImage::Format_RGB32);
|
||||
QPainter painter(&buffer);
|
||||
view.render(&painter, QRectF(QPointF(0, 0), QSizeF(area.size())), area);
|
||||
painter.end();
|
||||
|
||||
const QPalette dark = QET::Palette::fusionDark();
|
||||
const QColor base = dark.color(QPalette::Active, QPalette::Base);
|
||||
const QColor text = dark.color(QPalette::Active, QPalette::Text);
|
||||
QET::Palette::invertLightness(buffer, base, text);
|
||||
|
||||
QHash<QRgb, int> histogram;
|
||||
for (int y = 0; y < buffer.height(); ++y)
|
||||
for (int x = 0; x < buffer.width(); ++x)
|
||||
++histogram[buffer.pixel(x, y)];
|
||||
QRgb dominant = 0;
|
||||
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
|
||||
if (it.value() > histogram.value(dominant)) dominant = it.key();
|
||||
QCOMPARE(QColor(dominant), base);
|
||||
|
||||
const double contrast = inkContrast(buffer, buffer.rect());
|
||||
QVERIFY2(contrast >= QET::Palette::contrastRatio(base, text) - 0.5,
|
||||
qPrintable(QString("ink reads %1:1 on the dark sheet").arg(contrast)));
|
||||
|
||||
// The box at scene (20..60, 20..40) sits at (10..50, 10..30) in the buffer.
|
||||
const QColor box = buffer.pixelColor(30, 20);
|
||||
QVERIFY2(box.hslSaturationF() > 0.3 && box.blue() > box.red() + 60,
|
||||
qPrintable(QString("the blue box became %1").arg(box.name())));
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
A PaletteGraphicsView that counts its paints and records the
|
||||
paintingInverted() calls it receives.
|
||||
*/
|
||||
class ProbeView : public PaletteGraphicsView
|
||||
{
|
||||
public:
|
||||
int paints = 0;
|
||||
QList<QRect> paint_rects;
|
||||
QList<bool> inverted_calls;
|
||||
using PaletteGraphicsView::PaletteGraphicsView;
|
||||
/// True when a paint since index \a from covered the whole viewport.
|
||||
bool fullyRepaintedSince(int from) const
|
||||
{
|
||||
for (int i = from; i < paint_rects.size(); ++i)
|
||||
if (paint_rects.at(i).contains(viewport()->rect()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override
|
||||
{
|
||||
++paints;
|
||||
paint_rects << event->rect();
|
||||
PaletteGraphicsView::paintEvent(event);
|
||||
}
|
||||
void paintingInverted(bool inverted) override
|
||||
{
|
||||
inverted_calls << inverted;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
A small folio: a white sheet with a black line and a red box.
|
||||
Returns the box, which is selectable.
|
||||
*/
|
||||
QGraphicsRectItem *fillSheet(QGraphicsScene &scene)
|
||||
{
|
||||
scene.setSceneRect(0, 0, 200, 120);
|
||||
scene.setBackgroundBrush(Qt::white);
|
||||
scene.addLine(10, 60, 190, 60, QPen(Qt::black, 2));
|
||||
QGraphicsRectItem *box = scene.addRect(20, 20, 40, 20, QPen(Qt::NoPen), QBrush(QColor(200, 0, 0)));
|
||||
box->setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
return box;
|
||||
}
|
||||
|
||||
/// The view sized to its scene, without frame or scroll bars.
|
||||
void showAsSheet(QGraphicsView &view)
|
||||
{
|
||||
view.setFrameShape(QFrame::NoFrame);
|
||||
view.setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view.resize(200, 120);
|
||||
view.show();
|
||||
}
|
||||
|
||||
/// The most frequent color of an image: the sheet.
|
||||
QColor sheetColor(const QImage &image)
|
||||
{
|
||||
QHash<QRgb, int> histogram;
|
||||
for (int y = 0; y < image.height(); ++y)
|
||||
for (int x = 0; x < image.width(); ++x)
|
||||
++histogram[image.pixel(x, y)];
|
||||
QRgb best = 0;
|
||||
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
|
||||
if (it.value() > histogram.value(best)) best = it.key();
|
||||
return QColor(best);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Grid dots are black, white on a black sheet, and a third of the way
|
||||
from the sheet color to black when the sheet is about to be shown
|
||||
inverted, so that they do not come out as bright as the ink.
|
||||
*/
|
||||
void tst_qetpalette::gridDotColorSoftensInvertedDots()
|
||||
{
|
||||
QCOMPARE(QET::Palette::gridDotColor(Qt::white, false), QColor(Qt::black));
|
||||
QCOMPARE(QET::Palette::gridDotColor(Qt::white, true), QColor(170, 170, 170));
|
||||
QCOMPARE(QET::Palette::gridDotColor(Qt::darkGray, true), QColor(85, 85, 85));
|
||||
QCOMPARE(QET::Palette::gridDotColor(Qt::black, false), QColor(Qt::white));
|
||||
QCOMPARE(QET::Palette::gridDotColor(Qt::black, true), QColor(Qt::white));
|
||||
}
|
||||
|
||||
/**
|
||||
On a light palette the view shows the sheet as drawn and never tells
|
||||
the scene it inverts. On a dark palette, set while the view is
|
||||
showing, the sheet comes out as Base, the black line at text contrast,
|
||||
the red box still red, and the scene hears paintingInverted(true)
|
||||
before and (false) after. Back on a light palette the sheet is white
|
||||
again.
|
||||
*/
|
||||
void tst_qetpalette::paletteViewFollowsThePalette()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionLight());
|
||||
|
||||
QGraphicsScene scene;
|
||||
fillSheet(scene);
|
||||
ProbeView view(&scene);
|
||||
showAsSheet(view);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
|
||||
const QImage light = view.viewport()->grab().toImage();
|
||||
QCOMPARE(sheetColor(light), QColor(Qt::white));
|
||||
QVERIFY(view.inverted_calls.isEmpty());
|
||||
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
QTRY_VERIFY(view.invertsLightness());
|
||||
const QImage dark = view.viewport()->grab().toImage();
|
||||
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
|
||||
const QColor text = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Text);
|
||||
QCOMPARE(sheetColor(dark), base);
|
||||
const double contrast = inkContrast(dark, dark.rect());
|
||||
QVERIFY2(contrast >= QET::Palette::contrastRatio(base, text) - 0.5,
|
||||
qPrintable(QString("ink reads %1:1 on the dark sheet").arg(contrast)));
|
||||
const QColor box = dark.pixelColor(40, 30);
|
||||
QVERIFY2(box.hslHue() == 0 && box.hslSaturationF() > 0.3 && box.red() > box.blue() + 60,
|
||||
qPrintable(QString("the red box became %1").arg(box.name())));
|
||||
QVERIFY(!view.inverted_calls.isEmpty());
|
||||
QCOMPARE(view.inverted_calls.first(), true);
|
||||
QCOMPARE(view.inverted_calls.last(), false);
|
||||
QCOMPARE(view.inverted_calls.count(true), view.inverted_calls.count(false));
|
||||
|
||||
QApplication::setPalette(QET::Palette::fusionLight());
|
||||
QTRY_VERIFY(!view.invertsLightness());
|
||||
QCOMPARE(sheetColor(view.viewport()->grab().toImage()), QColor(Qt::white));
|
||||
}
|
||||
|
||||
/**
|
||||
QGraphicsView clears the scene's "update everything" flag only when it
|
||||
paints the items straight onto its viewport, and while the flag is set
|
||||
every further QGraphicsScene::update() and item update is dropped. On
|
||||
a dark palette the view paints through render() instead, so it listens
|
||||
to the scene's changed() signal, which makes the scene clear the flag
|
||||
before it emits. Three whole-scene updates and a selection must each
|
||||
repaint the view, with the scene set after construction as
|
||||
DiagramView does it.
|
||||
*/
|
||||
void tst_qetpalette::paletteViewKeepsSceneUpdatesFlowing()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
|
||||
QGraphicsScene scene;
|
||||
QGraphicsRectItem *box = fillSheet(scene);
|
||||
ProbeView view;
|
||||
view.setScene(&scene);
|
||||
showAsSheet(view);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
QTRY_VERIFY(view.paints >= 1);
|
||||
QVERIFY(view.invertsLightness());
|
||||
|
||||
for (int round = 1; round <= 3; ++round)
|
||||
{
|
||||
const int before = view.paints;
|
||||
scene.update();
|
||||
QTRY_VERIFY2(view.paints > before, qPrintable(QString("scene update %1 was dropped").arg(round)));
|
||||
}
|
||||
|
||||
const int before = view.paints;
|
||||
box->setSelected(true);
|
||||
QTRY_VERIFY2(view.paints > before, "the selection change was dropped");
|
||||
}
|
||||
|
||||
/**
|
||||
render() skips Qt's selection rubber band, so the view draws it after
|
||||
the inversion: while a drag on the sheet is in progress, the dragged
|
||||
area no longer shows the bare sheet.
|
||||
*/
|
||||
void tst_qetpalette::paletteViewDrawsTheRubberBand()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
|
||||
QGraphicsScene scene;
|
||||
fillSheet(scene);
|
||||
ProbeView view(&scene);
|
||||
view.setDragMode(QGraphicsView::RubberBandDrag);
|
||||
showAsSheet(view);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
|
||||
QCOMPARE(view.viewport()->grab().toImage().pixelColor(170, 100), base);
|
||||
|
||||
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(120, 80));
|
||||
QTest::mouseMove(view.viewport(), QPoint(190, 115));
|
||||
QTRY_VERIFY(!view.rubberBandRect().isNull());
|
||||
const QColor inside = view.viewport()->grab().toImage().pixelColor(170, 100);
|
||||
QVERIFY2(inside != base, qPrintable(QString("no rubber band drawn, pixel is %1").arg(inside.name())));
|
||||
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(190, 115));
|
||||
}
|
||||
|
||||
/**
|
||||
A style sheet on an ancestor pins the palette of every widget under it
|
||||
to the application palette in force when the sheet was applied
|
||||
(QStyleSheetStyle keeps its own copy; the folio tab widget carries
|
||||
such a sheet). The view's own palette() is therefore stale after a
|
||||
live switch, and the view must follow the application palette
|
||||
instead: inside a tab widget with a style sheet, switching the
|
||||
application to dark and back still changes the sheet. Qt also skips
|
||||
the repaint of a widget whose palette did not change, so the view
|
||||
has to repaint its whole viewport by itself on each switch, without
|
||||
anyone asking for a rendering: otherwise the area around the sheet
|
||||
keeps the old colors.
|
||||
*/
|
||||
void tst_qetpalette::paletteViewFollowsTheApplicationUnderAStyleSheet()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionLight());
|
||||
|
||||
QGraphicsScene scene;
|
||||
fillSheet(scene);
|
||||
QTabWidget tabs;
|
||||
tabs.setStyleSheet("QTabBar::scroller {width: 0px;}"); // as sources/projectview.cpp
|
||||
auto *view = new ProbeView(&scene);
|
||||
view->setFrameShape(QFrame::NoFrame);
|
||||
view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
tabs.addTab(view, "folio");
|
||||
tabs.resize(320, 240);
|
||||
tabs.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&tabs));
|
||||
QCOMPARE(sheetColor(view->viewport()->grab().toImage()), QColor(Qt::white));
|
||||
|
||||
int since = view->paint_rects.size();
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
QTRY_VERIFY(view->invertsLightness());
|
||||
QTRY_VERIFY2(view->fullyRepaintedSince(since), "no full repaint after the switch to dark");
|
||||
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
|
||||
QCOMPARE(sheetColor(view->viewport()->grab().toImage()), base);
|
||||
|
||||
since = view->paint_rects.size();
|
||||
QApplication::setPalette(QET::Palette::fusionLight());
|
||||
QTRY_VERIFY(!view->invertsLightness());
|
||||
QTRY_VERIFY2(view->fullyRepaintedSince(since), "no full repaint after the switch to light");
|
||||
QCOMPARE(sheetColor(view->viewport()->grab().toImage()), QColor(Qt::white));
|
||||
}
|
||||
|
||||
/**
|
||||
render() paints only what the scene draws. A scene without a
|
||||
background brush leaves the rest of the buffer untouched, so the view
|
||||
fills it first: on the dark palette the blank area comes out as Base,
|
||||
not as whatever the memory held.
|
||||
*/
|
||||
void tst_qetpalette::paletteViewFillsWhatTheSceneLeavesBlank()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
|
||||
QGraphicsScene scene(0, 0, 200, 120);
|
||||
scene.addLine(10, 60, 190, 60, QPen(Qt::black, 2));
|
||||
ProbeView view(&scene);
|
||||
showAsSheet(view);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
|
||||
const QImage image = view.viewport()->grab().toImage();
|
||||
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
|
||||
QCOMPARE(sheetColor(image), base);
|
||||
QCOMPARE(image.pixelColor(100, 20), base);
|
||||
QCOMPARE(image.pixelColor(100, 100), base);
|
||||
}
|
||||
|
||||
/**
|
||||
The inversion runs on every repaint of the folio, so a 4K viewport
|
||||
has to cost a few milliseconds. Reported, not asserted: the bound
|
||||
depends on the build box.
|
||||
*/
|
||||
void tst_qetpalette::invertLightnessSpeed()
|
||||
{
|
||||
QImage image(3840, 2000, QImage::Format_RGB32);
|
||||
image.fill(Qt::white);
|
||||
QBENCHMARK {
|
||||
QET::Palette::invertLightness(image, QColor(30, 30, 30), QColor(220, 220, 220));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Widgets are painted for real, on Qt's offscreen platform so the test
|
||||
|
||||
Reference in New Issue
Block a user