commit b585be367968b791e72d537b1501236bbcd560aa Author: uvos Date: Tue Jun 11 14:31:52 2024 +0200 initial commit 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
+
+
+ + +