From b585be367968b791e72d537b1501236bbcd560aa Mon Sep 17 00:00:00 2001 From: uvos Date: Tue, 11 Jun 2024 14:31:52 +0200 Subject: [PATCH] initial commit --- CMakeLists.txt | 76 +++++++++++++++++++++ imagemeta.cpp | 20 ++++++ imagemeta.h | 29 ++++++++ imagewidget.cpp | 39 +++++++++++ imagewidget.h | 18 +++++ main.cpp | 11 +++ mainwindow.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 42 ++++++++++++ mainwindow.ui | 153 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 561 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 imagemeta.cpp create mode 100644 imagemeta.h create mode 100644 imagewidget.cpp create mode 100644 imagewidget.h create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..99b858e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.5) + +project(QImageTagger VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui + imagemeta.h + imagemeta.cpp + imagewidget.h + imagewidget.cpp +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(QImageTagger + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET QImageTagger APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(QImageTagger SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") +else() + add_executable(QImageTagger + ${PROJECT_SOURCES} + ) +endif() +endif() + +target_link_libraries(QImageTagger PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.QImageTagger) +endif() +set_target_properties(QImageTagger PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS QImageTagger + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(QImageTagger) +endif() diff --git a/imagemeta.cpp b/imagemeta.cpp new file mode 100644 index 0000000..9cf7cc8 --- /dev/null +++ b/imagemeta.cpp @@ -0,0 +1,20 @@ +#include "imagemeta.h" + +ImageMeta::ImageMeta(const std::filesystem::path& path, const QString& text): + path{path}, text{text} +{ + +} + +ImageMeta::ImageMeta(const QJsonObject& json, const std::filesystem::path& dir) +{ + QJsonValue filename = json["file_name"]; + if(filename == QJsonValue::Undefined || !filename.isString()) + throw ParseException("No or invalid file_name field found"); + path = dir/filename.toString().toStdString(); + + QJsonValue textfrommeta = json["text"]; + text = textfrommeta.toString(""); +} + + diff --git a/imagemeta.h b/imagemeta.h new file mode 100644 index 0000000..7041fd2 --- /dev/null +++ b/imagemeta.h @@ -0,0 +1,29 @@ +#ifndef IMAGEMETA_H +#define IMAGEMETA_H + +#include +#include +#include +#include + +class ParseException : public QException +{ + QString msg; +public: + ParseException(const QString& msg): msg{msg} {} + QString getMesg() {return msg;}; + void raise() const override { throw *this; } + ParseException *clone() const override { return new ParseException(*this); } +}; + +class ImageMeta +{ +public: + std::filesystem::path path; + QString text; + + ImageMeta(const std::filesystem::path& path, const QString& text = ""); + ImageMeta(const QJsonObject& json, const std::filesystem::path& dir); +}; + +#endif // IMAGEMETA_H diff --git a/imagewidget.cpp b/imagewidget.cpp new file mode 100644 index 0000000..367f515 --- /dev/null +++ b/imagewidget.cpp @@ -0,0 +1,39 @@ +#include + +#include "imagewidget.h" + +void ImageWidget::paintEvent(QPaintEvent *event) +{ + QLabel::paintEvent(event); + displayImage(); +} + +void ImageWidget::setPixmap(QPixmap image) +{ + sourceImage = image; + currentImage = image; + repaint(); +} + +void ImageWidget::displayImage() +{ + if (sourceImage.isNull()) + return; + + float cw = width(), ch = height(); + float pw = currentImage.width(), ph = currentImage.height(); + + if(pw > cw && ph > ch && pw/cw > ph/ch || + pw > cw && ph <= ch || + pw < cw && ph < ch && cw/pw < ch/ph) + currentImage = sourceImage.scaledToWidth(cw, Qt::TransformationMode::FastTransformation); + else if(pw > cw && ph > ch && pw/cw <= ph/ch || + ph > ch && pw <= cw || + pw < cw && ph < ch && cw/pw > ch/ph) + currentImage = sourceImage.scaledToHeight(ch, Qt::TransformationMode::FastTransformation); + + int x = (cw - currentImage.width())/2, y = (ch - currentImage.height())/2; + + QPainter paint(this); + paint.drawPixmap(x, y, currentImage); +} diff --git a/imagewidget.h b/imagewidget.h new file mode 100644 index 0000000..5e8d019 --- /dev/null +++ b/imagewidget.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class ImageWidget: public QLabel +{ +private: + QPixmap sourceImage; + QPixmap currentImage; + + void displayImage(); + +public: + ImageWidget(QWidget* parent): QLabel(parent) { } + void setPixmap(QPixmap image); + void paintEvent(QPaintEvent *event); +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..1cd93da --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..3503d4e --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,173 @@ +#include "mainwindow.h" +#include "./ui_mainwindow.h" + +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + connect(ui->actionOpen, &QAction::triggered, this, [this](bool){open();}); + connect(ui->nextButton, &QPushButton::clicked, this, [this](bool){setImage(index+1);}); + connect(ui->prevButton, &QPushButton::clicked, this, [this](bool){setImage(index-1);}); + connect(ui->actionSave, &QAction::triggered, this, [this](bool){save();}); + connect(ui->actionQuit, &QAction::triggered, this, [this](bool){close();}); + ui->verticalLayout->addWidget(&status); + ui->imageLabel->setScaledContents( true ); + ui->imageLabel->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::open() +{ + QString dirpath = QFileDialog::getExistingDirectory(this, "Choose a directory with images", "", QFlags()); + if(dirpath.isEmpty()) + return; + + openDir(dirpath.toStdString()); +} + +void MainWindow::openDir(const std::filesystem::path& path) +{ + imageMetas.clear(); + + QDir dir(path); + bool hasMetadata = false; + std::vector imagePaths; + for(QString filename : dir.entryList(QDir::Files)) + { + if(filename == "metadata.jsonl") + hasMetadata = true; + else if(filename.endsWith(".jpg") || filename.endsWith(".png")) + imagePaths.push_back(path/filename.toStdString()); + } + + if(imagePaths.empty()) + { + QMessageBox::warning(this, "No images found", "No valid images found in this directory"); + return; + } + + if(hasMetadata) + { + if(!loadMetadata(path/"metadata.jsonl")) + QMessageBox::warning(this, "Could not read", + (std::string("Could not read ") + path.string() + std::string("/metadata.jsonl, file contains errors")).c_str()); + } + + for(const std::filesystem::path &path : imagePaths) + { + if(imageIsKnown(path)) + continue; + qDebug()<dir = path; + + ui->textEdit->document()->setPlainText(imageMetas[0].text); + setImage(0); +} + +void MainWindow::setImage(ssize_t index) +{ + if(imageMetas.empty()) + return; + else if(index > imageMetas.size()-1) + return; + else if(index < 0) + return; + + imageMetas[this->index].text = ui->textEdit->document()->toPlainText(); + this->index = index; + ui->counterLabel->setText(QString::asprintf("%04zu/%04zu", index, imageMetas.size()-1)); + ui->textEdit->document()->setPlainText(imageMetas[index].text); + + QPixmap image; + bool ret = image.load(imageMetas[index].path.string().c_str()); + if(!ret) + { + ui->imageLabel->setPixmap(QPixmap()); + ui->imageLabel->setText(("Unable to load " + imageMetas[index].path.string()).c_str()); + ui->imageLabel->setText("No image loaded"); + } + else + { + ui->imageLabel->setPixmap(image); + } + + ui->imageLabel->setText(""); +} + +bool MainWindow::loadMetadata(const std::filesystem::path& path) +{ + QFile meta(path); + meta.open(QIODeviceBase::ReadOnly); + if(!meta.isOpen()) + { + qWarning()<<("Could not open " + path.string()).c_str(); + return false; + } + + QByteArray line = meta.readLine(); + while(!line.isEmpty()) + { + QJsonParseError error; + QJsonDocument json = QJsonDocument::fromJson(line, &error); + if(json.isNull()) + { + qWarning()< +#include +#include + +#include "imagemeta.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + + bool modified = false; + std::vector imageMetas; + size_t index = 0; + std::filesystem::path dir; + QStatusBar status; + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void open(); + void save(); + +private: + void openDir(const std::filesystem::path& path); + bool loadMetadata(const std::filesystem::path& path); + bool imageIsKnown(const std::filesystem::path& path); + void setImage(ssize_t index); + + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..05e6aa8 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,153 @@ + + + MainWindow + + + + 0 + 0 + 1074 + 692 + + + + QImageTagger + + + + + + + 0 + + + 0 + + + + + + 24 + + + + No image loaded + + + Qt::AlignmentFlag::AlignCenter + + + 10 + + + + + + + + + + 0 + + + 0 + + + + + Prev + + + Left + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + 0000/0000 + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Next + + + Right + + + + + + + + + + + + + 0 + 0 + 1074 + 25 + + + + + File + + + + + + + + + + Open + + + Ctrl+O + + + + + Quit + + + + + Save + + + Ctrl+S + + + + + + ImageWidget + QLabel +
imagewidget.h
+
+
+ + +