diff --git a/.gitignore b/.gitignore index d163863..af5e6ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -build/ \ No newline at end of file +build/ +*.tar.gz +*.zip +*.png \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fde18a4..a2297c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(HDMIViewer VERSION 1.4.0 LANGUAGES CXX) +project(HDMIViewer VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..88b7269 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,28 @@ +# Maintainer: Dany Thinnes +pkgname=hdmi-viewer +pkgver=1.0.0 +pkgrel=1 +pkgdesc="Schlanker Qt6/C++ Live-Viewer für HDMI-Grabber (V4L2 + ALSA), Companion zu OBS Studio" +arch=('x86_64') +url="https://git.projekt-hirnfrei.de/diabolus/hdmiviewer" +license=('MIT') +depends=('qt6-base' 'alsa-lib') +makedepends=('cmake') +source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") +sha256sums=('SKIP') + +build() { + # Der Ordnername im Archiv hängt von der Gitea-Instanz ab (meist + # "hdmiviewer", nicht versioniert) - daher robust per Glob statt fest + # verdrahtetem Pfad. + cd "$srcdir"/*/ + cmake -B build -S . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr + cmake --build build +} + +package() { + cd "$srcdir"/*/ + DESTDIR="$pkgdir" cmake --install build +} diff --git a/README.md b/README.md index cab14f2..262b69d 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,87 @@ # HDMI Viewer -Ein schlanker Qt6/C++ HDMI-Viewer für Arch Linux mit V4L2 (Video) und ALSA (Audio). +Ein schlanker Qt6/C++ HDMI-Viewer für Arch Linux mit V4L2 (Video) und ALSA (Audio). Gedacht als Companion zu OBS Studio: **Anzeigen ohne Aufnehmen**. ## Features -- Live-Vorschau vom HDMI-Grabber via V4L2 (YUYV und NV12) -- ALSA-Audio direkt vom Grabber-Gerät -- Gerät-Auswahl beim Start, **Audio-Gerät wird gespeichert** +- Live-Vorschau vom HDMI-Grabber via V4L2, mit automatischer Formatpriorität + MJPEG → YUYV → NV12 (MJPEG braucht deutlich weniger USB-Bandbreite und + erlaubt so höhere Auflösungen/Framerates) +- ALSA-Audio direkt vom Grabber-Gerät (Capture → Wiedergabe, per Thread) +- Gerät-Auswahl beim Start (Video, Audio, Ziel-Framerate), alle Einstellungen + werden gespeichert und beim nächsten Start vorausgewählt - **F** → Vollbild / **F** oder **Esc** im Vollbild → Fenstermodus - **Esc** im Fenstermodus → zurück zur Gerät-Auswahl (Device wird sauber freigegeben) - Seitenverhältnis immer bewahrt (Letterbox/Pillarbox) -- Titelleiste zeigt tatsächliche Auflösung und FPS -- Colorspace-Konvertierung im ThreadPool (kein Ruckeln) -- FPS explizit auf 60fps gesetzt (was Grabber liefert wird in Statusbar angezeigt) -- **Bildglättung** (standardmäßig an): GPU-Shader trennt Luma/Chroma und - behandelt sie getrennt – Chroma wird stärker weichgezeichnet (killt - Dot-Crawl/Farbrauschen billiger Composite→HDMI-Scaler wie Lioncast Mini - AV2HDMI o.ä.), Luma wird leicht geglättet und dezent gegengeschärft - (nimmt harte Scaler-Kanten raus, ohne matschig zu wirken). Keine Optik- - Spielerei, sondern eine echte Bildverbesserung. Taste **S** zum Umschalten. -- **Retro-Optik** (standardmäßig aus): separater Nostalgie-Look – Scanlines, - Phosphor-/Aperture-Grille-Maske, leichte Wölbung wie bei einem alten - Röhrenfernseher. Lässt sich mit der Bildglättung kombinieren. Taste **C**. -- **Helligkeit** (Tasten **,** dunkler / **.** heller): Gamma-Anpassung, live - und persistiert. Sinnvoll als Feinjustierung, falls Farbraum-Erkennung - (siehe unten) allein nicht perfekt zum eigenen Bildschirm passt. -- **Hardware-Helligkeit**: Beim Öffnen des Geräts wird (falls vom Chip - unterstützt) der V4L2-Brightness-Regler direkt am Grabber moderat - angehoben (+20 % des Wertebereichs ab Werkseinstellung) – das wirkt - bereits VOR der JPEG-Kompression und kann so tatsächlich verlorene - Schattendetails retten, statt nur nachträglich zu strecken, was von der - Kompression übrig blieb. Diagnose-Ausgabe der verfügbaren Regler landet - beim Start im Terminal. +- Titelleiste und Statusleiste zeigen tatsächliche Auflösung, FPS und Pixelformat +- GPU-Rendering per OpenGL 3.3 (kein Ruckeln durch CPU-seitiges Skalieren) +- **Bildglättung** (standardmäßig an, Taste **S**): GPU-Shader trennt Luma/Chroma + und behandelt sie getrennt – Chroma wird stärker weichgezeichnet (killt + Dot-Crawl/Farbrauschen billiger Composite→HDMI-Scaler), Luma wird leicht + geglättet und dezent gegengeschärft (nimmt harte Scaler-Kanten raus, ohne + matschig zu wirken). Keine Optik-Spielerei, sondern eine echte Bildverbesserung. +- **Retro-Optik** (standardmäßig aus, Taste **C**): separater Nostalgie-Look – + Scanlines, Phosphor-/Aperture-Grille-Maske, leichte Wölbung wie bei einem + alten Röhrenfernseher. Lässt sich mit der Bildglättung kombinieren. +- **Nachschärfen** (Unsharp Mask, Taste **X** zum Umschalten, **+**/**-** zum + Fein-Justieren): unabhängig von der Bildglättung, live einstellbar und + persistiert. +- **Hardware-Helligkeit/-Kontrast/-Sättigung** (Tasten **,**/**.**, **1**/**2**, + **3**/**4**): steuert die entsprechenden V4L2-Regler *direkt am + Grabber-Chip*, also VOR der JPEG-Kompression – das rettet tatsächlich + Schattendetails, statt nur nachträglich am fertig komprimierten Bild zu + strecken (was sichtbare Artefakte/Banding erzeugt). Beim Start wird der + jeweilige Ausgangswert des Chips ausgelesen und gemerkt; beim Beenden wird + er exakt wiederhergestellt, sodass der Grabber für andere Programme + unverändert bleibt. Im Startdialog lassen sich für alle drei Regler feste + Zielwerte vorgeben (`unverändert` = Regler beim Start gar nicht anfassen). - **Studio-/Full-Range-Korrektur**: Das fertig dekodierte Bild wird von einem komprimierten Kontrastumfang (Schwarzpunkt niedrig angesetzt, Weißpunkt - ≈235/255) auf den vollen 0–255-Kontrastumfang gedehnt. Ohne diese - Korrektur wirkt das Bild dumpf, kontrastarm und zu dunkel – viele billige - HDMI-Grabber liefern (oder kodieren, auch bei MJPEG) genau in diesem - komprimierten Bereich. Greift direkt im GPU-Shader auf dem fertigen - RGB-Bild, also unabhängig vom Pixelformat. + ≈235/255) auf den vollen 0–255-Kontrastumfang gedehnt. Ohne diese Korrektur + wirkt das Bild dumpf, kontrastarm und zu dunkel – viele billige HDMI-Grabber + liefern (oder kodieren, auch bei MJPEG) genau in diesem komprimierten + Bereich. Greift direkt im GPU-Shader auf dem fertigen RGB-Bild, also + unabhängig vom Pixelformat. +- **Roh-Modus** (Taste **R**): Diagnose-Umschalter, der sämtliche Verarbeitung + (Bildglättung, Retro-Optik, Studio-Range-Korrektur, Kontrast-/ + Sättigungs-Punch, Gamma) umgeht und das Bild 1:1 anzeigt. +- **Live-Vergleich** (Taste **V**): zeigt links das rohe und rechts das + bearbeitete Bild nebeneinander – praktisch zum Fein-Tunen der Hardware-Regler. +- **Screenshot** (Taste **P**): speichert das aktuell angezeigte (bearbeitete) + Frame als PNG im Home-Verzeichnis. +- **Frame-Dump** (Taste **D**, nur bei MJPEG): speichert das nächste rohe, + unveränderte MJPEG-Frame direkt vom Chip als JPEG – nützlich, um zu prüfen, + ob Abweichungen schon in den Rohdaten liegen oder erst bei der eigenen + Verarbeitung entstehen. ## Abhängigkeiten (Arch Linux) ```bash -sudo pacman -S qt6-base cmake alsa-lib linux-headers base-devel +sudo pacman -S qt6-base cmake alsa-lib base-devel ``` `qt6-base` bringt auf Arch bereits die benötigten `Qt6OpenGL`- und -`Qt6OpenGLWidgets`-Module mit. +`Qt6OpenGLWidgets`-Module mit. `linux/videodev2.h` ist Teil von +`linux-api-headers`, das über die `base`-Gruppe ohnehin installiert ist. -## Bauen +## Installation über AUR + +```bash +yay -S hdmi-viewer +# oder mit einem anderen AUR-Helper, bzw. manuell: +git clone https://aur.archlinux.org/hdmi-viewer.git +cd hdmi-viewer +makepkg -si +``` + +## Quellcode + +```bash +git clone https://git.projekt-hirnfrei.de/diabolus/hdmiviewer.git +``` + +## Manuell bauen ```bash mkdir build && cd build @@ -66,17 +97,25 @@ make -j$(nproc) ## Tastenkürzel -| Taste | Funktion | -|--------------|---------------------------------------------| -| F | Vollbild ein/aus | -| S | Bildglättung ein/aus | -| C | Retro-Optik (Scanlines/Maske) ein/aus | -| , / . | Dunkler / Heller (Helligkeit, gespeichert) | -| Esc | Vollbild verlassen ODER zurück zum Dialog | +| Taste | Funktion | +|---------|--------------------------------------------------------| +| F | Vollbild ein/aus | +| S | Bildglättung ein/aus | +| C | Retro-Optik (Scanlines/Maske) ein/aus | +| X | Nachschärfen ein/aus (letzter Wert ↔ aus) | +| + / - | Nachschärfen stärker / schwächer | +| , / . | Hardware-Helligkeit dunkler / heller | +| 1 / 2 | Hardware-Kontrast runter / hoch | +| 3 / 4 | Hardware-Sättigung runter / hoch | +| R | Roh-Modus (Diagnose, alle Effekte umgangen) ein/aus | +| V | Live-Vergleich (links roh, rechts bearbeitet) ein/aus | +| P | Screenshot speichern (PNG) | +| D | Rohes MJPEG-Frame dumpen (Diagnose) | +| Esc | Vollbild verlassen ODER zurück zum Gerätedialog | ## Einstellungen -Werden gespeichert in: `~/.config/hdmi-viewer/HDMIViewer.conf` +Werden gespeichert in: `~/.config/hdmi-viewer/HDMIViewer.conf` (Standard XDG-Pfad, kein manuelles Anlegen nötig) ## Audio-Gerät finden @@ -90,5 +129,5 @@ arecord -D hw:2,0 -f S16_LE -r 48000 -c 2 /tmp/test.wav aplay /tmp/test.wav ``` -Das Gerät dann im Dialog so eingeben: z.B. `hw:2,0` oder `plughw:2,0` +Das Gerät dann im Dialog so eingeben: z.B. `hw:2,0` oder `plughw:2,0` (plughw macht automatische Sample-Rate-Konvertierung, falls nötig) diff --git a/src/CaptureThread.cpp b/src/CaptureThread.cpp index 51f6874..7ff4a38 100644 --- a/src/CaptureThread.cpp +++ b/src/CaptureThread.cpp @@ -392,7 +392,7 @@ void CaptureThread::restoreHardwareControls() qDebug().noquote() << "Hardware-Regler auf Ausgangswerte zurückgesetzt."; } -// Für Live-Tasten (Kontrast/Sättigung): liest den aktuellen Wert, addiert +// Für Live-Tasten (Helligkeit/Kontrast/Sättigung): liest den aktuellen Wert, addiert // delta, schreibt zurück, merkt sich den neuen Wert dauerhaft und meldet // ihn per Signal (für die Einblendung in der UI). void CaptureThread::adjustHardwareControl(quint32 id, const char* settingsKey, @@ -422,6 +422,11 @@ void CaptureThread::adjustHardwareControl(quint32 id, const char* settingsKey, } } +void CaptureThread::adjustHardwareBrightness(int delta) +{ + adjustHardwareControl(V4L2_CID_BRIGHTNESS, "hardware/brightness", "Hardware-Helligkeit", delta); +} + void CaptureThread::adjustHardwareContrast(int delta) { adjustHardwareControl(V4L2_CID_CONTRAST, "hardware/contrast", "Hardware-Kontrast", delta); diff --git a/src/CaptureThread.h b/src/CaptureThread.h index bbd0e82..ade3afa 100644 --- a/src/CaptureThread.h +++ b/src/CaptureThread.h @@ -24,6 +24,7 @@ public: void stopAndWait(); void requestFrameDump(); // speichert das nächste rohe MJPEG-Frame unverändert auf die Platte + void adjustHardwareBrightness(int delta); void adjustHardwareContrast(int delta); void adjustHardwareSaturation(int delta); diff --git a/src/DeviceDialog.cpp b/src/DeviceDialog.cpp index 11ac951..0d261fb 100644 --- a/src/DeviceDialog.cpp +++ b/src/DeviceDialog.cpp @@ -1,5 +1,5 @@ #include "DeviceDialog.h" -#include "VideoWidget.h" // für kBrightnessMin/Max, kSharpenMin/Max (eine Quelle der Wahrheit) +#include "VideoWidget.h" // für kSharpenMin/Max (eine Quelle der Wahrheit) #include #include @@ -85,16 +85,17 @@ DeviceDialog::DeviceDialog(QWidget* parent) "0 = aus. Im laufenden Betrieb mit [X]/[+]/[-] änderbar."); form->addRow("Nachschärfen:", m_sharpenSpin); - m_brightnessSpin = new QDoubleSpinBox(this); - m_brightnessSpin->setRange(VideoWidget::kBrightnessMin * 100.0, VideoWidget::kBrightnessMax * 100.0); - m_brightnessSpin->setSingleStep(VideoWidget::kBrightnessStep * 100.0); - m_brightnessSpin->setDecimals(0); - m_brightnessSpin->setSuffix(" %"); - m_brightnessSpin->setValue(100.0); - m_brightnessSpin->setToolTip( - "Gamma-Helligkeit, 100 % = unverändert. Im laufenden Betrieb\n" - "mit [,]/[.] änderbar."); - form->addRow("Helligkeit:", m_brightnessSpin); + m_hwBrightnessSpin = new QSpinBox(this); + m_hwBrightnessSpin->setRange(-1, 100); + m_hwBrightnessSpin->setValue(-1); + m_hwBrightnessSpin->setSpecialValueText("unverändert"); + m_hwBrightnessSpin->setToolTip( + "Hardware-Helligkeit direkt am Grabber-Chip, VOR der JPEG-Kompression\n" + "(Wertebereich hängt vom jeweiligen Chip ab, hier 0-100 angenommen).\n" + "\"unverändert\" lässt den aktuellen Chip-Wert einfach in Ruhe.\n" + "Im laufenden Betrieb mit [,]/[.] änderbar. hdmi-viewer merkt sich\n" + "beim Start den Ausgangswert und stellt ihn beim Beenden wieder her."); + form->addRow("Hardware-Helligkeit:", m_hwBrightnessSpin); m_hwContrastSpin = new QSpinBox(this); m_hwContrastSpin->setRange(-1, 100); @@ -125,7 +126,7 @@ DeviceDialog::DeviceDialog(QWidget* parent) "Titelleiste angezeigt.
" "F Vollbild  ·  S Glättung  ·  " "C Retro  ·  X/+/- Schärfen  ·  " - ",/. Helligkeit  ·  1/2 HW-Kontrast  ·  " + ",/. HW-Helligkeit  ·  1/2 HW-Kontrast  ·  " "3/4 HW-Sättigung  ·  V Vergleich  ·  " "P Screenshot  ·  Esc zurück hierher", this); @@ -156,7 +157,7 @@ int DeviceDialog::selectedFps() const { return m_fpsCombo->currentDa bool DeviceDialog::selectedSmoothMode() const { return m_smoothCheck->isChecked(); } bool DeviceDialog::selectedRetroMode() const { return m_retroCheck->isChecked(); } float DeviceDialog::selectedSharpenAmount() const { return static_cast(m_sharpenSpin->value()); } -float DeviceDialog::selectedBrightness() const { return static_cast(m_brightnessSpin->value() / 100.0); } +int DeviceDialog::selectedHwBrightness() const { return m_hwBrightnessSpin->value(); } int DeviceDialog::selectedHwContrast() const { return m_hwContrastSpin->value(); } int DeviceDialog::selectedHwSaturation() const { return m_hwSaturationSpin->value(); } @@ -166,7 +167,7 @@ void DeviceDialog::setPreselectedFps(int fps) { selectByData(m_fpsCombo, QString void DeviceDialog::setPreselectedSmoothMode(bool enabled) { m_smoothCheck->setChecked(enabled); } void DeviceDialog::setPreselectedRetroMode(bool enabled) { m_retroCheck->setChecked(enabled); } void DeviceDialog::setPreselectedSharpenAmount(float amount) { m_sharpenSpin->setValue(amount); } -void DeviceDialog::setPreselectedBrightness(float gamma) { m_brightnessSpin->setValue(gamma * 100.0); } +void DeviceDialog::setPreselectedHwBrightness(int value) { m_hwBrightnessSpin->setValue(value); } void DeviceDialog::setPreselectedHwContrast(int value) { m_hwContrastSpin->setValue(value); } void DeviceDialog::setPreselectedHwSaturation(int value) { m_hwSaturationSpin->setValue(value); } diff --git a/src/DeviceDialog.h b/src/DeviceDialog.h index ac13f32..b572a8c 100644 --- a/src/DeviceDialog.h +++ b/src/DeviceDialog.h @@ -23,7 +23,7 @@ public: bool selectedSmoothMode() const; bool selectedRetroMode() const; float selectedSharpenAmount() const; - float selectedBrightness() const; + int selectedHwBrightness() const; // -1 = unverändert lassen int selectedHwContrast() const; // -1 = unverändert lassen int selectedHwSaturation() const; // -1 = unverändert lassen @@ -33,7 +33,7 @@ public: void setPreselectedSmoothMode(bool enabled); void setPreselectedRetroMode(bool enabled); void setPreselectedSharpenAmount(float amount); - void setPreselectedBrightness(float gamma); + void setPreselectedHwBrightness(int value); // -1 = unverändert void setPreselectedHwContrast(int value); // -1 = unverändert void setPreselectedHwSaturation(int value); // -1 = unverändert @@ -47,8 +47,8 @@ private: QComboBox* m_fpsCombo = nullptr; QCheckBox* m_smoothCheck = nullptr; QCheckBox* m_retroCheck = nullptr; - QDoubleSpinBox* m_sharpenSpin = nullptr; - QDoubleSpinBox* m_brightnessSpin = nullptr; - QSpinBox* m_hwContrastSpin = nullptr; + QDoubleSpinBox* m_sharpenSpin = nullptr; + QSpinBox* m_hwBrightnessSpin = nullptr; + QSpinBox* m_hwContrastSpin = nullptr; QSpinBox* m_hwSaturationSpin = nullptr; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 980cb73..e8000a1 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -25,7 +25,6 @@ MainWindow::MainWindow(const QString& videoDevice, int targetFps, bool smoothModeEnabled, bool retroModeEnabled, - float brightness, float sharpenAmount, float lastSharpenAmount, QWidget* parent) @@ -41,7 +40,6 @@ MainWindow::MainWindow(const QString& videoDevice, m_video->setSmoothModeEnabled(smoothModeEnabled); m_video->setRetroModeEnabled(retroModeEnabled); m_video->setSharpenAmount(sharpenAmount); - m_video->setBrightness(brightness); setCentralWidget(m_video); // Kurzzeitige Einblendung unten links (funktioniert auch im Vollbild, @@ -118,11 +116,11 @@ void MainWindow::setupMenuBar() viewMenu->addSeparator(); - QAction* brighterAct = viewMenu->addAction("Heller\t."); - connect(brighterAct, &QAction::triggered, this, &MainWindow::increaseBrightness); + QAction* brighterAct = viewMenu->addAction("Heller (Hardware)\t."); + connect(brighterAct, &QAction::triggered, this, &MainWindow::increaseHwBrightness); - QAction* darkerAct = viewMenu->addAction("Dunkler\t,"); - connect(darkerAct, &QAction::triggered, this, &MainWindow::decreaseBrightness); + QAction* darkerAct = viewMenu->addAction("Dunkler (Hardware)\t,"); + connect(darkerAct, &QAction::triggered, this, &MainWindow::decreaseHwBrightness); viewMenu->addSeparator(); @@ -230,14 +228,20 @@ void MainWindow::applySharpen(float value) showOverlayMessage(QString("Nachschärfen: %1").arg(value, 0, 'f', 1)); } -void MainWindow::increaseBrightness() +void MainWindow::increaseHwBrightness() { - applyBrightness(m_video->brightness() + VideoWidget::kBrightnessStep); + if (m_capture) + { + m_capture->adjustHardwareBrightness(5); + } } -void MainWindow::decreaseBrightness() +void MainWindow::decreaseHwBrightness() { - applyBrightness(m_video->brightness() - VideoWidget::kBrightnessStep); + if (m_capture) + { + m_capture->adjustHardwareBrightness(-5); + } } void MainWindow::toggleRaw() @@ -279,19 +283,6 @@ void MainWindow::saveScreenshot() } } -void MainWindow::applyBrightness(float value) -{ - value = qBound(VideoWidget::kBrightnessMin, value, VideoWidget::kBrightnessMax); - m_video->setBrightness(value); - - QSettings settings; - settings.setValue("display/brightness", static_cast(value)); - - statusBar()->showMessage( - QString("Helligkeit: %1 %").arg(qRound(value * 100.0f)), 2000); - showOverlayMessage(QString("Helligkeit: %1 %").arg(qRound(value * 100.0f))); -} - void MainWindow::showAbout() { auto* dlg = new QDialog(this); @@ -345,7 +336,7 @@ void MainWindow::showAbout() grid->addWidget(v, row, 1); }; - addRow(0, "Version", "1.4.0"); + addRow(0, "Version", "1.0"); addRow(1, "Entwickler", "Dany Thinnes"); addRow(2, "Projekt", "Projekt Hirnfrei"); addRow(3, "Framework", QString("Qt %1").arg(QT_VERSION_STR)); @@ -365,7 +356,7 @@ void MainWindow::showAbout() "S Glättung    " "C Retro    " "X/+/- Schärfen    " - ",/. Helligkeit
" + ",/. HW-Helligkeit
" "1/2 HW-Kontrast    " "3/4 HW-Sättigung    " "R Roh-Diagnose
" @@ -442,10 +433,10 @@ void MainWindow::keyPressEvent(QKeyEvent* event) decreaseSharpen(); break; case Qt::Key_Period: - increaseBrightness(); + increaseHwBrightness(); break; case Qt::Key_Comma: - decreaseBrightness(); + decreaseHwBrightness(); break; case Qt::Key_R: toggleRaw(); @@ -503,7 +494,7 @@ void MainWindow::updateStatusBar() info = QString("%1×%2 @ %3fps %4 ") .arg(m_capWidth).arg(m_capHeight).arg(m_capFps).arg(m_capFmt); - info += QString("Video: %1 Audio: %2 [F] Vollbild [S] Glättung [C] Retro [X/+/-] Schärfen [,/.] Helligkeit [1/2] HW-Kontrast [3/4] HW-Sättigung [R] Roh [V] Vergleich [P] Screenshot [D] Frame dumpen [Esc] Einstellungen") + info += QString("Video: %1 Audio: %2 [F] Vollbild [S] Glättung [C] Retro [X/+/-] Schärfen [,/.] HW-Helligkeit [1/2] HW-Kontrast [3/4] HW-Sättigung [R] Roh [V] Vergleich [P] Screenshot [D] Frame dumpen [Esc] Einstellungen") .arg(m_videoDevice, m_audioDevice.isEmpty() ? QStringLiteral("–") : m_audioDevice); diff --git a/src/MainWindow.h b/src/MainWindow.h index 07ca98e..42dfe1f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -21,7 +21,6 @@ public: int targetFps, bool smoothModeEnabled = true, bool retroModeEnabled = false, - float brightness = 1.0f, float sharpenAmount = 0.0f, float lastSharpenAmount = 2.5f, QWidget* parent = nullptr); @@ -44,8 +43,8 @@ private slots: void toggleSharpen(); void increaseSharpen(); void decreaseSharpen(); - void increaseBrightness(); - void decreaseBrightness(); + void increaseHwBrightness(); + void decreaseHwBrightness(); void toggleRaw(); void toggleCompare(); void saveScreenshot(); @@ -55,7 +54,6 @@ private: void toggleFullscreen(); void stopAll(); void updateStatusBar(); - void applyBrightness(float value); void applySharpen(float value); void showOverlayMessage(const QString& text); diff --git a/src/main.cpp b/src/main.cpp index 2c15779..5a041f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ int main(int argc, char* argv[]) QApplication app(argc, argv); app.setApplicationName("HDMIViewer"); - app.setApplicationVersion("1.4.0"); + app.setApplicationVersion("1.0"); app.setOrganizationName("hdmi-viewer"); QSettings settings; @@ -31,7 +31,7 @@ int main(int argc, char* argv[]) dlg.setPreselectedSmoothMode(settings.value("display/smooth", true).toBool()); dlg.setPreselectedRetroMode(settings.value("display/retro", false).toBool()); dlg.setPreselectedSharpenAmount(settings.value("display/sharpen", 0.0).toFloat()); - dlg.setPreselectedBrightness(settings.value("display/brightness", 1.0).toFloat()); + dlg.setPreselectedHwBrightness(settings.value("hardware/brightness", -1).toInt()); dlg.setPreselectedHwContrast(settings.value("hardware/contrast", -1).toInt()); dlg.setPreselectedHwSaturation(settings.value("hardware/saturation", -1).toInt()); @@ -44,7 +44,7 @@ int main(int argc, char* argv[]) const bool smoothMode = dlg.selectedSmoothMode(); const bool retroMode = dlg.selectedRetroMode(); const float sharpenAmt = dlg.selectedSharpenAmount(); - const float brightness = dlg.selectedBrightness(); + const int hwBrightness = dlg.selectedHwBrightness(); const int hwContrast = dlg.selectedHwContrast(); const int hwSaturation = dlg.selectedHwSaturation(); // Für die Taste [X] (Quick-Toggle 0 ↔ letzter Wert) zur Laufzeit: @@ -66,11 +66,14 @@ int main(int argc, char* argv[]) settings.setValue("display/smooth", smoothMode); settings.setValue("display/retro", retroMode); settings.setValue("display/sharpen", static_cast(sharpenAmt)); - settings.setValue("display/brightness", static_cast(brightness)); if (sharpenAmt > 0.0f) settings.setValue("display/sharpen_last", static_cast(sharpenAmt)); // -1 = "unverändert" -> kein expliziter Nutzerwert, Regler bleibt // beim Öffnen unangetastet (siehe CaptureThread::applyHardwareControls) + if (hwBrightness >= 0) + settings.setValue("hardware/brightness", hwBrightness); + else + settings.remove("hardware/brightness"); if (hwContrast >= 0) settings.setValue("hardware/contrast", hwContrast); else @@ -81,7 +84,7 @@ int main(int argc, char* argv[]) settings.remove("hardware/saturation"); settings.sync(); - MainWindow win(videoDevice, audioDevice, fps, smoothMode, retroMode, brightness, sharpenAmt, lastSharpen); + MainWindow win(videoDevice, audioDevice, fps, smoothMode, retroMode, sharpenAmt, lastSharpen); win.show(); app.exec();