Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
199f95a
commit 2c3cbf6
Showing
22 changed files
with
700 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
QT += quick | ||
CONFIG += c++11 | ||
DEFINES += QT_DEPRECATED_WARNINGS | ||
SOURCES += cpp/main.cpp cpp/datamodels.cpp cpp/dofcalcobject.cpp | ||
RESOURCES += qml.qrc | ||
HEADERS += cpp/datamodels.h cpp/dofcalcobject.h cpp/dofstrings.h | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,32 @@ | ||
# DOFCalc | ||
Depth of Field Calculator for mobile | ||
# DOFCalc v0.1 | ||
## README | ||
|
||
This is my first application for mobile phones, and my first on Qt+QML. | ||
|
||
When I started studying QML, I decided that it might be useful to write a small but complete program that I will use myself. | ||
Aside from this, the DOF calculators that I found for mobile platforms seemed very overloaded: when I'm out shooting, I need to quickly input three parameters (focal length, aperture, distance) and get the result (hyperfocal distance, near and far limits of acceptable sharpness). I don't want to see anything else; this is where the idea for the program was born. | ||
|
||
It has no (common in the modern day) bells and whistles, such as | ||
* setting the circle of confusion by choosing from a million options ("Nikon, APS-C", "Canon APS-H"...), the circle is set directly, in microns | ||
* teleconverter support (meaning automatic re-calculation of lens speed and focal length). | ||
|
||
## Notes | ||
When I had a working prototype ready, I noticed that there did not turn out to be too much code, and decided, out of sporting interest, to try to use as little code as possible with minimal losses in readability. | ||
For this reason: | ||
* the braces are set differently than I usually do, but that minimizes the number of lines, | ||
* any repetition was mercilessly generalized, there are almost no repeated pieces of code, | ||
* data models are designed based on ideas of compactness, not readability, | ||
With these tricks I was able to keep entire project below 600 lines (before copyright statements were added), this is good enough for some really useful project. | ||
|
||
## TODO | ||
* I'm not happy with automated font size selection code, it works under Android (any reasonable device screen size), but not on Windows. It should be some more elegant way to set default font size in QML (any advices are welcome). | ||
* Build/deploy files are not included. Only code and minimal qmake .pro file. | ||
|
||
The project is not done yet: there are other things a photographer (more precisely, I myself) needs in the field; so the work is to be quietly continued. | ||
|
||
## Licensing | ||
DOFCalc is (C) Copyright 2019 by Alex Tutubalin, LibRaw LLC, lexa@libraw.org. All Right Reserved | ||
|
||
DOFCalc is distributed under the terms of GNU General Public License v3 | ||
|
||
Alex Tutubalin, LibRaw LLC, Mar 28, 2019 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* DOFCalc v0.1 | ||
(C)opyright 2019 Alex Tutubalin, LibRaw LLC, lexa@libraw.org | ||
The program is licensed under the terms of GNU General Public License v3 */ | ||
|
||
#include "datamodels.h" | ||
#include <QSet> | ||
#include <QRegularExpression> | ||
|
||
QVariant focalModel::data(const QModelIndex &index, int role /* = Qt::DisplayRole */) const { | ||
int row = index.row(); | ||
if (row < 0 || row >= rowCount()) return QVariant(); | ||
switch (role) { | ||
case DOFDisplayRole: if (!datalist[row].big) return S_longdash; // fall-through | ||
case DOFPrintRole: return formatString.arg(datalist[row].value, 0, 'f', digits(datalist[row].value)); | ||
case DOFValueRole: return datalist[row].value; | ||
case DOFSizeRole: return int(datalist[row].big); | ||
} | ||
return QVariant(); | ||
} | ||
|
||
void focalModel::populate0(const QHash<QString, int>&) { | ||
struct focal_params_t { qreal begin, step; int div; }; | ||
QList<focal_params_t> focal_params(QList<focal_params_t>{ {5, 1, 5}, { 30,5,10, }, { 150,10,50 }, { 250,25,50 }, { 400,50,100 }, { 1000,100,200 }, { 1600,0,100 } }); | ||
for (int i = 0; i < focal_params.size(); i++) { | ||
datalist.push_back(floatitem_t(focal_params[i].begin, true)); | ||
if (focal_params[i].step < 0.001) break; | ||
qreal start = focal_params[i].begin + focal_params[i].step; | ||
while (start < focal_params[i + 1].begin) { | ||
datalist.push_back(floatitem_t(start, !(int(start) % focal_params[i].div))); | ||
start += focal_params[i].step; | ||
} | ||
} | ||
} | ||
|
||
void apertureModel::populate0(const QHash<QString, int>& params) { | ||
QList<qreal>fstoplist[4] = { // log2 based calculations do not match widely used aperture scales; 1/2 and 1/3 scales are copied from Seconic L558 scale; 1/4 scale matches seconic 1/2 | ||
{QList<qreal>{0.7, 1, 1.4, 2, 2.8, 4, 5.6, 8, 11, 16, 22, 32, 45, 64, 90, 128, 180, 256}}, | ||
{QList<qreal>{0.7, 0.8, 1, 1.2,1.4,1.7, 2,2.4, 2.8, 3.4, 4, 4.8, 5.6, 6.7, 8, 9.5, 11,13, 16,19, 22,27, 32,38, 45,54, 64, 76, 90, 108, 128, 152, 180, 215, 256}}, | ||
{QList<qreal>{0.7, 0.8, 0.9, 1, 1.1,1.3, 1.4, 1.6, 1.8, 2, 2.2, 2.5, 2.8, 3.2, 3.6, 4, 4.5, 5, 5.6, 6.3, 7.1, 8, 9, 10, 11, 13,14, 16, 18, 20, 22, 25, 29, 32, | ||
36, 40, 45, 51, 57, 64,72, 81, 90, 102,114, 128,144, 161, 180,200,225,256 }}, | ||
{QList<qreal>{0.7,0.75, 0.8,0.9, 1, 1.1, 1.2, 1.3,1.4, 1.5, 1.7,1.8, 2,2.2, 2.4,2.6, 2.8, 3.1, 3.4, 3.7, 4,4.4,4.8, 5.2, 5.6,6.2, 6.7,7.3, 8,8.7, 9.5,10, 11,12,13, | ||
15,16,17,19,21,22,25,27,29,32,34,38,42,45,50,54,58,64,68,76,84,90,100,108,116,128, 135, 152, 165, 180, 200, 215, 230, 256}}, | ||
}; | ||
|
||
int ns = qBound(0, params[s_aperture], 3); | ||
for (int i = 0; i < fstoplist[ns].size(); i++) | ||
datalist.push_back(floatitem_t(fstoplist[ns][i], !(i % (ns + 1)))); | ||
} | ||
|
||
QString cutzeroes(qreal v, int digits) { | ||
QString s = QString("%1").arg(v, 0, 'f', digits); | ||
if (s.contains('.')) | ||
s.replace(QRegularExpression("\\.0+$"), ""); | ||
if (s.contains('.')) | ||
s.replace(QRegularExpression("0+$"), ""); | ||
return s; | ||
} | ||
|
||
QVariant distanceModel::data(const QModelIndex &index, int role /* = Qt::DisplayRole */) const { | ||
if (index.row() >= 0 && index.row() < rowCount() && role == DOFDisplayRole && datalist[index.row()].big) | ||
return formatString.arg(cutzeroes(datalist[index.row()].value, digits(datalist[index.row()].value))); | ||
return focalModel::data(index, role); | ||
} | ||
|
||
void distanceModel::populate0(const QHash<QString, int>& settings) { | ||
QList<qreal> l10 = QList<qreal>{ 0.1, 0.125, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.75, 1 }; // Again: manual distance list is much better than generated one | ||
QSet<int> B = QSet<int>{ 0, 3, 5, 7 }; | ||
int f1 = qBound(1, settings[s_distance] + 1, 3); | ||
for (int i = 0; i < 4; i++) | ||
for (int j = 0; j < l10.size() - 1; j++) { | ||
datalist.push_back(floatitem_t(std::pow(10.0, qreal(i))*l10[j], f1 > 1 || B.contains(j))); | ||
for (int k = 1; k < f1; k++) | ||
datalist.push_back(floatitem_t(l10[j] * std::exp(std::log(l10[j + 1] / l10[j]) / qreal(f1)*k)*std::pow(10.0, qreal(i)), false)); | ||
} | ||
datalist.push_back(floatitem_t(1000, true)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* DOFCalc v0.1 | ||
(C)opyright 2019 Alex Tutubalin, LibRaw LLC, lexa@libraw.org | ||
The program is licensed under the terms of GNU General Public License v3 */ | ||
|
||
#pragma once | ||
#include <QAbstractListModel> | ||
#include "dofstrings.h" | ||
#include <cmath> | ||
|
||
enum DOFRoles { DOFDisplayRole = Qt::UserRole + 1, DOFValueRole, DOFSizeRole, DOFPrintRole }; | ||
|
||
struct floatitem_t { | ||
qreal value; | ||
bool big; | ||
floatitem_t() : value(1), big(false) {} | ||
floatitem_t(qreal v, bool b) : value(v), big(b) {} | ||
floatitem_t(const floatitem_t& s) : value(s.value), big(s.big) {} | ||
}; | ||
|
||
class focalModel : public QAbstractListModel { | ||
Q_OBJECT | ||
public: | ||
focalModel(QObject *parent = nullptr) : QAbstractListModel(parent), formatString(f_focal) { } | ||
int rowCount(const QModelIndex &parent = QModelIndex()) const { Q_UNUSED(parent); return datalist.size(); } | ||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||
QHash<int, QByteArray> roleNames() const { | ||
return QHash<int, QByteArray>{ {DOFDisplayRole, "displayname"}, { DOFValueRole, "value" }, { DOFSizeRole, "rowsize" }}; | ||
} | ||
void populate(const QHash<QString, int>& s) { beginResetModel(); datalist.clear(); populate0(s); endResetModel(); } | ||
protected: | ||
virtual void populate0(const QHash<QString, int>& s); | ||
virtual int digits(qreal) const { return 0; } | ||
QString formatString; | ||
QList<floatitem_t> datalist; | ||
}; | ||
|
||
class apertureModel : public focalModel { | ||
Q_OBJECT | ||
public: | ||
apertureModel(QObject *parent = nullptr) : focalModel(parent) { formatString = QString(f_aperture); } | ||
protected: | ||
virtual void populate0(const QHash<QString, int>&); | ||
virtual int digits(qreal val) const { | ||
if (val > 12) return 0; | ||
else if (val >= 1) return (std::abs(val - std::round(val)) < 0.1 ? 0 : 1); | ||
else return (std::abs(val * 10 - std::round(val * 10)) < 0.5 ? 1 : 2); | ||
} | ||
}; | ||
|
||
class distanceModel : public focalModel { | ||
Q_OBJECT | ||
public: | ||
distanceModel(QObject *parent = nullptr) : focalModel(parent) { formatString = QString(f_distance); } | ||
virtual void populate0(const QHash<QString, int>&); | ||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||
protected: | ||
virtual int digits(qreal val) const { return int(qMax(0.0, std::round(2.0 - std::log10(val)))); } | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* DOFCalc v0.1 | ||
(C)opyright 2019 Alex Tutubalin, LibRaw LLC, lexa@libraw.org | ||
The program is licensed under the terms of GNU General Public License v3 */ | ||
|
||
#include "dofcalcobject.h" | ||
#include <QGuiApplication> | ||
#include <QQmlContext> | ||
#include <QWindow> | ||
#include <QScreen> | ||
|
||
DOFCalculator::DOFCalculator(QQmlApplicationEngine *e, QObject* parent /* = 0 */) : QObject(parent), engine(e) { | ||
QSize screensz = QGuiApplication::screens().at(0)->availableSize(); | ||
int fs = QSysInfo::productType() == QString("windows") ? 18 : qBound(8, qMin(screensz.width(), screensz.height()) / 32, 30); | ||
settings.loadSettings(fs); | ||
params.loadSettings(); | ||
setFontAndHeading(settings.values[s_fontSize]); | ||
|
||
datamodels[mv_focal] = new focalModel(); | ||
datamodels[mv_aperture] = new apertureModel(); | ||
datamodels[mv_distance] = new distanceModel(); | ||
|
||
foreach(const QString& mname, datamodels.keys()) | ||
{ | ||
datamodels[mname]->populate(settings.values); | ||
engine->rootContext()->setContextProperty(QString(t_Name2Model).arg(mname), datamodels[mname]); | ||
} | ||
} | ||
|
||
void DOFCalculator::setViewIndexAndShow(const QString &mname, const QString& object, QObject* flist) { | ||
int index = findNearestIndex(datamodels[mname], params.params[mname]); | ||
if (index >= 0) { | ||
savedVelocity[object] = flist->property(p_highlightMoveVelocity).toInt(); | ||
flist->setProperty(p_highlightMoveVelocity, 99999); | ||
flist->setProperty(p_currentIndex, index); | ||
setText(QString(t_Name2DisplayField).arg(mname), datamodels[mname]->index(index).data(DOFPrintRole).toString()); | ||
} | ||
} | ||
|
||
void DOFCalculator::setFontAndHeading(int fontsize) { | ||
engine->rootContext()->setContextProperty("globalFontSize", settings.values[s_fontSize]); | ||
if (QWindow *w = (QGuiApplication::topLevelWindows().size() > 0 ? QGuiApplication::topLevelWindows().at(0) : nullptr)) { | ||
int bigfont = (qMin(w->geometry().width(), w->geometry().height()) / qMax(1, fontsize) < 28); | ||
setText("hyperfocalPrefix", bigfont ? "HF:" : "HFocal:"); | ||
setText("dofFarPrefix", bigfont ? "F:" : "Far:"); | ||
setText("dofNearPrefix", bigfont ? "N:" : "Near:"); | ||
} | ||
} | ||
|
||
void DOFCalculator::afterQMLStart(QGuiApplication* app) { | ||
connect(app, &QGuiApplication::applicationStateChanged, this, &DOFCalculator::appStateChanged); | ||
|
||
setFontAndHeading(settings.values[s_fontSize]); | ||
if (QObject *dialog = findObjectByName("settingsDialog")) | ||
connect(dialog, SIGNAL(settingsDialogChanged()), this, SLOT(settingsChanged())); | ||
|
||
foreach(const QString& mname, datamodels.keys()) { | ||
QString object = QString(t_Name2ListView).arg(mname); | ||
if (QObject *flist = findObjectByName(object)) { | ||
scrollers[mname] = flist; | ||
setViewIndexAndShow(mname, object, flist); | ||
connect(flist, SIGNAL(myViewIndexChanged(int)), this, SLOT(scrollerChanged(int))); | ||
} | ||
} | ||
foreach(const QString& key, settings.values.keys()) { | ||
QObject *obj = nullptr; | ||
if (key.startsWith(t_IsIndexVar) && (obj = findObjectByName(key.right(t_IndexNameLen)))) | ||
obj->setProperty(p_currentIndex, settings.values[key]); | ||
else if ((obj = findObjectByName(key))) | ||
obj->setProperty(p_value, settings.values[key]); | ||
} | ||
calc(); | ||
} | ||
|
||
int DOFCalculator::repopulate(const QString& mname) { | ||
if (!datamodels[mname] || !scrollers[mname]) return 0; | ||
QString object = QString(t_Name2ListView).arg(mname); | ||
if (QObject *flist = findObjectByName(object)) { | ||
scrollers[mname]->blockSignals(1); | ||
datamodels[mname]->populate(settings.values); | ||
setViewIndexAndShow(mname, object, flist); | ||
scrollers[mname]->blockSignals(0); | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
|
||
void DOFCalculator::settingsChanged() { | ||
dofsettings_t osettings = settings; | ||
foreach(const QString& key, settings.values.keys()) { | ||
QObject *obj = nullptr; | ||
if (key.startsWith(t_IsIndexVar) && (obj = findObjectByName(key.right(t_IndexNameLen)))) | ||
settings.values[key] = obj->property(p_currentIndex).toInt(); | ||
else if ((obj = findObjectByName(key))) | ||
settings.values[key] = obj->property(p_value).toInt(); | ||
} | ||
if (osettings.values == settings.values) return; | ||
settings.saveSettings(); | ||
int repopulated = 0; | ||
if (settings.values[s_aperture] != osettings.values[s_aperture]) repopulated += repopulate(mv_aperture); | ||
if (settings.values[s_distance] != osettings.values[s_distance]) repopulated += repopulate(mv_distance); | ||
if (repopulated) QTimer::singleShot(2000, this, SLOT(restoreVelocities())); | ||
setFontAndHeading(settings.values[s_fontSize]); | ||
calc(); | ||
} | ||
|
||
void DOFCalculator::scrollerChanged(int dindex) { | ||
dofparams_t oparams = params; | ||
QString key; | ||
foreach(const QString& mn, scrollers.keys()) | ||
if (scrollers[mn] == sender()) | ||
key = mn; | ||
if (key.length() && datamodels.contains(key) && dindex >= 0 && dindex < datamodels[key]->rowCount()) { | ||
params.params[key] = datamodels[key]->index(dindex).data(DOFValueRole).toReal(); | ||
if (params.params == oparams.params) return; | ||
calc(); | ||
setText(QString(t_Name2DisplayField).arg(key), datamodels[key]->index(dindex).data(DOFPrintRole).toString()); | ||
} | ||
} | ||
|
||
int DOFCalculator::findNearestIndex(QAbstractListModel* model, qreal wanted) { | ||
if (model->rowCount() < 2) return model->rowCount() - 1; | ||
qreal current = model->index(0).data(DOFValueRole).toReal(); | ||
qreal diff = qAbs(current - wanted); | ||
for (int i = 1; i < model->rowCount(); i++) { | ||
qreal val = model->index(i).data(DOFValueRole).toReal(); | ||
if (diff < qAbs(val - wanted)) return i - 1; | ||
diff = qAbs(val - wanted); | ||
} | ||
return model->rowCount() - 1; | ||
} | ||
|
||
QObject* DOFCalculator::findObjectByName(const QString& object) { | ||
foreach(QObject* o, engine->rootObjects()) | ||
if (QObject *item = o->findChild<QObject*>(object)) | ||
return item; | ||
return nullptr; | ||
} | ||
|
||
void DOFCalculator::restoreVelocities() { | ||
foreach(const QString& oname, savedVelocity.keys()) | ||
if (QObject * item = findObjectByName(oname)) | ||
item->setProperty(p_highlightMoveVelocity, savedVelocity[oname]); | ||
savedVelocity.clear(); | ||
} | ||
|
||
void DOFCalculator::calc() { | ||
qreal focalM = params.params[mv_focal] / 1000.0, CoCM = qreal(settings.values["coc"]) / 1000.0 / 1000.0, distance = params.params[mv_distance], aperture = params.params[mv_aperture]; | ||
qreal airy = 0.38 * aperture / 1000.0 / 1000.0; // 0.38um - about shortest visible wavelength | ||
CoCM = qMax(CoCM, airy); | ||
qreal focalM2 = focalM * focalM; | ||
qreal H = focalM2 / aperture / CoCM + focalM; | ||
setText("hyperfocalString", QString(f_distance).arg(H, 0, 'f', int(qMax(0.0, std::round(2.0 - std::log10(H)))))); | ||
if (focalM > distance * 0.9) | ||
setDOF(S_longdash, S_longdash); | ||
else | ||
{ | ||
qreal DOFNear = distance * focalM2 / (focalM2 - aperture * focalM * CoCM + aperture * distance * CoCM); | ||
qreal DOFFar = distance * focalM2 / (focalM2 + aperture * focalM * CoCM - aperture * distance * CoCM); | ||
|
||
if (DOFFar > 0) { | ||
int chars = qMax(0, int(1 - int(std::log10((DOFFar - DOFNear) / 10)))); | ||
setDOF(QString(f_distance).arg(DOFNear, 0, 'f', chars), QString(f_distance).arg(DOFFar, 0, 'f', chars)); | ||
} | ||
else | ||
setDOF(QString(f_distance).arg(DOFNear, 0, 'f', qMax(0, 2 - int(std::log10(DOFNear)))), QString(QChar(0x221e))); | ||
} | ||
} |
Oops, something went wrong.