mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 05:00:20 +00:00
profile: reimplement DivePercentageItem
The tissue percentages were realized as 16 independent polygons. That didn't work at all with the new absolute scaling. Reimplement the item and blast it onto a pixmap. Not only is this artifact-free, it also should (hopefully) be quite a bit more efficient than painting numerous lines. In contrast to the old code, this does access the plot_info structure directly instead of using the model. Not so much for performance reason, but rather to make things more robust: We have a strongly typed language. Why would we shoehorn data through the weakly typed QVariant and mess with wierd index-arithmetics. Makes no sense to me. Qt-model have to be used for interfacing with Qt. They are terrible for intra-application data transfer. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
24cf6709e3
commit
505e4e47eb
10 changed files with 177 additions and 89 deletions
|
@ -168,6 +168,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||
profile-widget/qmlprofile.cpp \
|
||||
profile-widget/divecartesianaxis.cpp \
|
||||
profile-widget/diveeventitem.cpp \
|
||||
profile-widget/divepercentageitem.cpp \
|
||||
profile-widget/diveprofileitem.cpp \
|
||||
profile-widget/profilescene.cpp \
|
||||
profile-widget/animationfunctions.cpp \
|
||||
|
@ -321,6 +322,7 @@ HEADERS += \
|
|||
qt-models/filterconstraintmodel.h \
|
||||
qt-models/filterpresetmodel.h \
|
||||
profile-widget/qmlprofile.h \
|
||||
profile-widget/divepercentageitem.h \
|
||||
profile-widget/diveprofileitem.h \
|
||||
profile-widget/profilescene.h \
|
||||
profile-widget/diveeventitem.h \
|
||||
|
|
|
@ -10,6 +10,8 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
|||
divelineitem.h
|
||||
divepixmapitem.cpp
|
||||
divepixmapitem.h
|
||||
divepercentageitem.cpp
|
||||
divepercentageitem.h
|
||||
diveprofileitem.cpp
|
||||
diveprofileitem.h
|
||||
diverectitem.cpp
|
||||
|
|
|
@ -359,6 +359,12 @@ double DiveCartesianAxis::minimum() const
|
|||
return min;
|
||||
}
|
||||
|
||||
std::pair<double, double> DiveCartesianAxis::screenMinMax() const
|
||||
{
|
||||
return position == Position::Bottom ? std::make_pair(rect.left(), rect.right())
|
||||
: std::make_pair(rect.top(), rect.bottom());
|
||||
}
|
||||
|
||||
void DiveCartesianAxis::setColor(const QColor &color)
|
||||
{
|
||||
QPen defaultPen = gridPen();
|
||||
|
|
|
@ -41,6 +41,7 @@ public:
|
|||
void setFontLabelScale(qreal scale);
|
||||
double minimum() const;
|
||||
double maximum() const;
|
||||
std::pair<double, double> screenMinMax() const;
|
||||
qreal valueAt(const QPointF &p) const;
|
||||
qreal posAtValue(qreal value) const;
|
||||
void setColor(const QColor &color);
|
||||
|
@ -48,7 +49,7 @@ public:
|
|||
void animateChangeLine(const QRectF &rect, int animSpeed);
|
||||
void setTextVisible(bool arg1);
|
||||
void setLinesVisible(bool arg1);
|
||||
void setLine(const QLineF& line);
|
||||
void setLine(const QLineF &line);
|
||||
virtual void updateTicks(int animSpeed);
|
||||
double width() const; // only for vertical axes
|
||||
double height() const; // only for horizontal axes
|
||||
|
|
132
profile-widget/divepercentageitem.cpp
Normal file
132
profile-widget/divepercentageitem.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "divepercentageitem.h"
|
||||
#include "divecartesianaxis.h"
|
||||
#include "core/dive.h"
|
||||
#include "core/profile.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
DivePercentageItem::DivePercentageItem(const DiveCartesianAxis &hAxis, const DiveCartesianAxis &vAxis, double dpr) :
|
||||
hAxis(hAxis), vAxis(vAxis), dpr(dpr)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr int num_tissues = 16;
|
||||
|
||||
// Calculate the number of scanlines for every drawn tissue.
|
||||
static std::array<int, num_tissues> calcLinesPerTissue(int size)
|
||||
{
|
||||
std::array<int, num_tissues> res;
|
||||
|
||||
// A Bresenham-inspired algorithm without the weird half steps at the beginning and the end.
|
||||
if (size <= 0) {
|
||||
std::fill(res.begin(), res.end(), 0);
|
||||
} else if (size >= num_tissues) {
|
||||
int step = size / num_tissues;
|
||||
int err_inc = size % num_tissues;
|
||||
int err = 0;
|
||||
for (int i = 0; i < num_tissues; ++i) {
|
||||
res[i] = step;
|
||||
err += err_inc;
|
||||
if (err >= num_tissues) {
|
||||
err -= num_tissues;
|
||||
++res[i];
|
||||
}
|
||||
}
|
||||
} else { // size < num_tissues
|
||||
int step = num_tissues / size;
|
||||
int err_inc = num_tissues % size;
|
||||
int err = 0;
|
||||
int act = 0;
|
||||
std::fill(res.begin(), res.end(), 0);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
res[act] = 1;
|
||||
act += step;
|
||||
err += err_inc;
|
||||
if (err >= size) {
|
||||
err -= size;
|
||||
++act;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static QRgb colorScale(double value, int inert)
|
||||
{
|
||||
QColor color;
|
||||
double scaledValue = value / (AMB_PERCENTAGE * inert) * 1000.0;
|
||||
if (scaledValue < 0.8) // grade from cyan to blue to purple
|
||||
color.setHsvF(0.5 + 0.25 * scaledValue / 0.8, 1.0, 1.0);
|
||||
else if (scaledValue < 1.0) // grade from magenta to black
|
||||
color.setHsvF(0.75, 1.0, (1.0 - scaledValue) / 0.2);
|
||||
else if (value < AMB_PERCENTAGE) // grade from black to bright green
|
||||
color.setHsvF(0.333, 1.0, (value - AMB_PERCENTAGE * inert / 1000.0) / (AMB_PERCENTAGE - AMB_PERCENTAGE * inert / 1000.0));
|
||||
else if (value < 65) // grade from bright green (0% M) to yellow-green (30% M)
|
||||
color.setHsvF(0.333 - 0.133 * (value - AMB_PERCENTAGE) / (65.0 - AMB_PERCENTAGE), 1.0, 1.0);
|
||||
else if (value < 85) // grade from yellow-green (30% M) to orange (70% M)
|
||||
color.setHsvF(0.2 - 0.1 * (value - 65.0) / 20.0, 1.0, 1.0);
|
||||
else if (value < 100) // grade from orange (70% M) to red (100% M)
|
||||
color.setHsvF(0.1 * (100.0 - value) / 15.0, 1.0, 1.0);
|
||||
else if (value < 120) // M value exceeded - grade from red to white
|
||||
color.setHsvF(0.0, 1 - (value - 100.0) / 20.0, 1.0);
|
||||
else // white
|
||||
color.setHsvF(0.0, 0.0, 1.0);
|
||||
return color.rgba();
|
||||
}
|
||||
|
||||
void DivePercentageItem::replot(const dive *d, const struct divecomputer *dc, const plot_info &pi)
|
||||
{
|
||||
auto [minX, maxX] = hAxis.screenMinMax();
|
||||
auto [minY, maxY] = vAxis.screenMinMax();
|
||||
int width = lrint(maxX) - lrint(minX);
|
||||
int height = lrint(maxY) - lrint(minY);
|
||||
if (width <= 0 || height <= 0) {
|
||||
setPixmap(QPixmap());
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<int, num_tissues> linesPerTissue = calcLinesPerTissue(height);
|
||||
|
||||
QImage img(width, height, QImage::QImage::Format_ARGB32);
|
||||
|
||||
int line = 0;
|
||||
for (int tissue = 0; tissue < num_tissues; ++tissue) {
|
||||
if (linesPerTissue[tissue] <= 0)
|
||||
continue;
|
||||
int x = 0;
|
||||
QRgb *scanline = (QRgb *)img.scanLine(line);
|
||||
QRgb color = 0;
|
||||
const struct event *ev = NULL;
|
||||
for (int i = 0; i < pi.nr; i++) {
|
||||
const plot_data &item = pi.entry[i];
|
||||
int sec = item.sec;
|
||||
int nextX = lrint(hAxis.posAtValue(sec)) - lrint(minX);
|
||||
if (nextX == x)
|
||||
continue;
|
||||
|
||||
double value = item.percentages[tissue];
|
||||
struct gasmix gasmix = get_gasmix(d, dc, sec, &ev, gasmix);
|
||||
int inert = get_n2(gasmix) + get_he(gasmix);
|
||||
color = colorScale(value, inert);
|
||||
if (nextX >= width)
|
||||
nextX = width - 1;
|
||||
for (; x <= nextX; ++x)
|
||||
scanline[x] = color;
|
||||
if (nextX >= width - 1)
|
||||
break;
|
||||
}
|
||||
for (; x < width; ++x)
|
||||
scanline[x] = color;
|
||||
++line;
|
||||
|
||||
// Clone line if needed
|
||||
for (int i = 0; i < linesPerTissue[tissue] - 1; ++i) {
|
||||
QRgb *scanline2 = (QRgb *)img.scanLine(line);
|
||||
std::copy(scanline, scanline + width, scanline2);
|
||||
++line;
|
||||
}
|
||||
}
|
||||
setPixmap(QPixmap::fromImage(img));
|
||||
setPos(minX, minY);
|
||||
}
|
23
profile-widget/divepercentageitem.h
Normal file
23
profile-widget/divepercentageitem.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef DIVEPERCENTAGEITEM_H
|
||||
#define DIVEPERCENTAGEITEM_H
|
||||
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
struct dive;
|
||||
struct divecomputer;
|
||||
struct plot_info;
|
||||
class DivePlotDataModel;
|
||||
class DiveCartesianAxis;
|
||||
|
||||
class DivePercentageItem : public QGraphicsPixmapItem {
|
||||
public:
|
||||
DivePercentageItem(const DiveCartesianAxis &hAxis, const DiveCartesianAxis &vAxis, double dpr);
|
||||
void replot(const dive *d, const divecomputer *dc, const plot_info &pi);
|
||||
private:
|
||||
const DiveCartesianAxis &hAxis;
|
||||
const DiveCartesianAxis &vAxis;
|
||||
int hDataColumn;
|
||||
double dpr;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -233,74 +233,6 @@ void DiveHeartrateItem::paint(QPainter *painter, const QStyleOptionGraphicsItem*
|
|||
painter->restore();
|
||||
}
|
||||
|
||||
DivePercentageItem::DivePercentageItem(const DivePlotDataModel &model, const DiveCartesianAxis &hAxis, int hColumn,
|
||||
const DiveCartesianAxis &vAxis, int vColumn, int i, double dpr) :
|
||||
AbstractProfilePolygonItem(model, hAxis, hColumn, vAxis, vColumn, dpr),
|
||||
tissueIndex(i)
|
||||
{
|
||||
}
|
||||
|
||||
void DivePercentageItem::replot(const dive *d, bool)
|
||||
{
|
||||
// Ignore empty values. a heart rate of 0 would be a bad sign.
|
||||
QPolygonF poly;
|
||||
colors.clear();
|
||||
for (int i = 0, modelDataCount = dataModel.rowCount(); i < modelDataCount; i++) {
|
||||
int sec = dataModel.index(i, hDataColumn).data().toInt();
|
||||
QPointF point(hAxis.posAtValue(sec), vAxis.posAtValue(64 - 4 * tissueIndex));
|
||||
poly.append(point);
|
||||
|
||||
double value = dataModel.index(i, vDataColumn).data().toDouble();
|
||||
struct gasmix gasmix = gasmix_air;
|
||||
const struct event *ev = NULL;
|
||||
gasmix = get_gasmix(d, get_dive_dc_const(d, dc_number), sec, &ev, gasmix);
|
||||
int inert = get_n2(gasmix) + get_he(gasmix);
|
||||
colors.push_back(ColorScale(value, inert));
|
||||
}
|
||||
setPolygon(poly);
|
||||
}
|
||||
|
||||
QColor DivePercentageItem::ColorScale(double value, int inert)
|
||||
{
|
||||
QColor color;
|
||||
double scaledValue = value / (AMB_PERCENTAGE * inert) * 1000.0;
|
||||
if (scaledValue < 0.8) // grade from cyan to blue to purple
|
||||
color.setHsvF(0.5 + 0.25 * scaledValue / 0.8, 1.0, 1.0);
|
||||
else if (scaledValue < 1.0) // grade from magenta to black
|
||||
color.setHsvF(0.75, 1.0, (1.0 - scaledValue) / 0.2);
|
||||
else if (value < AMB_PERCENTAGE) // grade from black to bright green
|
||||
color.setHsvF(0.333, 1.0, (value - AMB_PERCENTAGE * inert / 1000.0) / (AMB_PERCENTAGE - AMB_PERCENTAGE * inert / 1000.0));
|
||||
else if (value < 65) // grade from bright green (0% M) to yellow-green (30% M)
|
||||
color.setHsvF(0.333 - 0.133 * (value - AMB_PERCENTAGE) / (65.0 - AMB_PERCENTAGE), 1.0, 1.0);
|
||||
else if (value < 85) // grade from yellow-green (30% M) to orange (70% M)
|
||||
color.setHsvF(0.2 - 0.1 * (value - 65.0) / 20.0, 1.0, 1.0);
|
||||
else if (value < 100) // grade from orange (70% M) to red (100% M)
|
||||
color.setHsvF(0.1 * (100.0 - value) / 15.0, 1.0, 1.0);
|
||||
else if (value < 120) // M value exceeded - grade from red to white
|
||||
color.setHsvF(0.0, 1 - (value - 100.0) / 20.0, 1.0);
|
||||
else // white
|
||||
color.setHsvF(0.0, 0.0, 1.0);
|
||||
return color;
|
||||
|
||||
}
|
||||
|
||||
void DivePercentageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem*, QWidget*)
|
||||
{
|
||||
if (polygon().isEmpty())
|
||||
return;
|
||||
painter->save();
|
||||
QPen mypen;
|
||||
mypen.setCapStyle(Qt::FlatCap);
|
||||
mypen.setCosmetic(false);
|
||||
QPolygonF poly = polygon();
|
||||
for (int i = 1; i < poly.count(); i++) {
|
||||
mypen.setBrush(QBrush(colors[i]));
|
||||
painter->setPen(mypen);
|
||||
painter->drawLine(poly[i - 1], poly[i]);
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
DiveTemperatureItem::DiveTemperatureItem(const DivePlotDataModel &model, const DiveCartesianAxis &hAxis, int hColumn,
|
||||
const DiveCartesianAxis &vAxis, int vColumn, double dpr) :
|
||||
AbstractProfilePolygonItem(model, hAxis, hColumn, vAxis, vColumn, dpr)
|
||||
|
|
|
@ -97,21 +97,6 @@ private:
|
|||
QString visibilityKey;
|
||||
};
|
||||
|
||||
class DivePercentageItem : public AbstractProfilePolygonItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DivePercentageItem(const DivePlotDataModel &model, const DiveCartesianAxis &hAxis, int hColumn, const DiveCartesianAxis &vAxis, int vColumn, int i, double dpr);
|
||||
void replot(const dive *d, bool in_planner) override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
|
||||
private:
|
||||
std::vector<QColor> colors; // Must have same number of elements as the polygon
|
||||
QString visibilityKey;
|
||||
int tissueIndex;
|
||||
QColor ColorScale(double value, int inert);
|
||||
|
||||
};
|
||||
|
||||
class DiveGasPressureItem : public AbstractProfilePolygonItem {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "profilescene.h"
|
||||
#include "diveeventitem.h"
|
||||
#include "divecartesianaxis.h"
|
||||
#include "divepercentageitem.h"
|
||||
#include "diveprofileitem.h"
|
||||
#include "divetextitem.h"
|
||||
#include "tankitem.h"
|
||||
|
@ -69,6 +70,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|||
diveCeiling(createItem<DiveCalculatedCeiling>(*profileYAxis, DivePlotDataModel::CEILING, 1, dpr)),
|
||||
decoModelParameters(new DiveTextItem(dpr, 1.0, Qt::AlignHCenter | Qt::AlignTop, nullptr)),
|
||||
heartBeatItem(createItem<DiveHeartrateItem>(*heartBeatAxis, DivePlotDataModel::HEARTBEAT, 1, dpr)),
|
||||
percentageItem(new DivePercentageItem(*timeAxis, *percentageAxis, dpr)),
|
||||
tankItem(new TankItem(*timeAxis, dpr))
|
||||
{
|
||||
init_plot_info(&plotInfo);
|
||||
|
@ -119,10 +121,10 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|||
for (int i = 0; i < 16; i++) {
|
||||
DiveCalculatedTissue *tissueItem = createItem<DiveCalculatedTissue>(*profileYAxis, DivePlotDataModel::TISSUE_1 + i, i + 1, dpr);
|
||||
allTissues.append(tissueItem);
|
||||
DivePercentageItem *percentageItem = createItem<DivePercentageItem>(*percentageAxis, DivePlotDataModel::PERCENTAGE_1 + i, i + 1, i, dpr);
|
||||
allPercentages.append(percentageItem);
|
||||
}
|
||||
|
||||
percentageItem->setZValue(1.0);
|
||||
|
||||
// Add items to scene
|
||||
addItem(diveComputerText);
|
||||
addItem(tankItem);
|
||||
|
@ -134,6 +136,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|||
addItem(cylinderPressureAxis);
|
||||
addItem(percentageAxis);
|
||||
addItem(heartBeatAxis);
|
||||
addItem(percentageItem);
|
||||
|
||||
for (AbstractProfilePolygonItem *item: profileItems)
|
||||
addItem(item);
|
||||
|
@ -189,8 +192,7 @@ void ProfileScene::updateVisibility()
|
|||
#ifndef SUBSURFACE_MOBILE
|
||||
for (DiveCalculatedTissue *tissue: allTissues)
|
||||
tissue->setVisible(prefs.calcalltissues && prefs.calcceiling);
|
||||
for (DivePercentageItem *percentage: allPercentages)
|
||||
percentage->setVisible(prefs.percentagegraph);
|
||||
percentageItem->setVisible(prefs.percentagegraph);
|
||||
#endif
|
||||
meanDepthItem->setVisible(prefs.show_average_depth);
|
||||
reportedCeiling->setVisible(prefs.dcceiling);
|
||||
|
@ -468,6 +470,9 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
|||
for (AbstractProfilePolygonItem *item: profileItems)
|
||||
item->replot(d, inPlanner);
|
||||
|
||||
if (prefs.percentagegraph)
|
||||
percentageItem->replot(d, currentdc, dataModel->data());
|
||||
|
||||
// The event items are a bit special since we don't know how many events are going to
|
||||
// exist on a dive, so I cant create cache items for that. that's why they are here
|
||||
// while all other items are up there on the constructor.
|
||||
|
|
|
@ -96,7 +96,7 @@ private:
|
|||
DiveTextItem *decoModelParameters;
|
||||
QList<DiveCalculatedTissue *> allTissues;
|
||||
DiveHeartrateItem *heartBeatItem;
|
||||
QList<DivePercentageItem *> allPercentages;
|
||||
DivePercentageItem *percentageItem;
|
||||
TankItem *tankItem;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue