From 82cf65f474e3faea7e4fbe1fc2141508aee26595 Mon Sep 17 00:00:00 2001 From: KevinT3Hu Date: Sun, 19 May 2024 22:36:22 +0800 Subject: [PATCH] Initial commit --- .clang-format | 190 +++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + CMakeLists.txt | 59 ++++++++++++ CMakePresets.json | 42 +++++++++ include/adb.h | 7 ++ qt.cmake | 17 ++++ source/ImageCropper.cpp | 195 ++++++++++++++++++++++++++++++++++++++++ source/ImageCropper.h | 62 +++++++++++++ source/ImageCropper.ui | 49 ++++++++++ source/QCropWidget.cpp | 128 ++++++++++++++++++++++++++ source/QCropWidget.h | 51 +++++++++++ source/adb.cpp | 76 ++++++++++++++++ source/main.cpp | 10 +++ 13 files changed, 888 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 include/adb.h create mode 100644 qt.cmake create mode 100644 source/ImageCropper.cpp create mode 100644 source/ImageCropper.h create mode 100644 source/ImageCropper.ui create mode 100644 source/QCropWidget.cpp create mode 100644 source/QCropWidget.h create mode 100644 source/adb.cpp create mode 100644 source/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3daef24 --- /dev/null +++ b/.clang-format @@ -0,0 +1,190 @@ +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# for clang-format 17.0.1 +Language: Cpp +BasedOnStyle: "WebKit" +# AccessModifierOffset: 2 +AlignAfterOpenBracket: "AlwaysBreak" +AlignArrayOfStructures: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: None +# AlignConsecutiveShortCaseStatements: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 1 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +# AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +# AttributeMacros: +# - __pragma +# - _Pragma +# - __attribute__ +# - __declspec +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: After +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: true + # AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +# BracedInitializerIndentWidth: 4 +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: true +BreakArrays: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Attach +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 100 +# CommentPragmas: '^ MEO pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +# ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IfMacros: +# - 'KJ_IF_MAYBE' +IncludeBlocks: Preserve +# IncludeCategories: +# IncludeIsMainRegex: +# IncludeIsMainSourceRegex: +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: false +IndentPPDirectives: None +IndentRequiresClause: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +InsertTrailingCommas: Wrapped +IntegerLiteralSeparator: + Binary: 4 + BinaryMinDigits: 9 + Decimal: 3 + DecimalMinDigits: 7 + Hex: -1 +# JavaImportGroups: +# JavaScriptQuotes: +# JavaScriptWrapImports: +KeepEmptyLinesAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +# LineEnding: LF +# MacroBlockBegin: "MAA.*_NS_BEGIN$" +# MacroBlockEnd: "MAA.*_NS_END$" +# Macros: +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +# NamespaceMacros: +# ObjCBinPackProtocolList: +# ObjCBlockIndentWidth: +# ObjCBreakBeforeNestedBlockParam: +# ObjCSpaceAfterProperty: +# ObjCSpaceBeforeProtocolList: +PackConstructorInitializers: Never +# PenaltyBreakAssignment: +# PenaltyBreakBeforeFirstCallParameter: +# PenaltyBreakComment: +# PenaltyBreakFirstLessLess: +# PenaltyBreakOpenParenthesis: +# PenaltyBreakTemplateDeclaration: +# PenaltyExcessCharacter: +# PenaltyIndentedWhitespace: +# PenaltyReturnTypeOnItsOwnLine: +PointerAlignment: Left +# QualifierAlignment: Custom +# QualifierOrder: +# - inline +# - static +# - const +# - constexpr +# - type +ReferenceAlignment: Left +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Always +ShortNamespaceLines: 1000 +SortIncludes: CaseSensitive +# SortJavaStaticImport: +SortUsingDeclarations: Lexicographic +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +# SpaceBeforeParensOptions: +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInSquareBrackets: false +Standard: c++20 +# StatementAttributeLikeMacros: +# StatementMacros: +TabWidth: 4 +# TypeNames: +# TypenameMacros: +UseTab: Never +# VerilogBreakBetweenInstancePorts: +# WhitespaceSensitiveMacros: \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..268336e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +.vs diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a49c3b4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.16) +project(ImageCropper LANGUAGES CXX) + +include(qt.cmake) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} + COMPONENTS + Core + Gui + Widgets +) +qt_standard_project_setup() + +set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source) + +file(GLOB PROJECT_SOURCES ${SOURCE_DIR}/*) + +set(GLOB FORMS ${SOURCE_DIR}/*.ui) + +source_group(FORMS FILES ${FORMS}) + +qt_add_executable(ImageCropper ${PROJECT_SOURCES}) + +### OpenCV + +find_package(OpenCV REQUIRED) +target_include_directories(ImageCropper + PRIVATE + ${OpenCV_INCLUDE_DIRS} +) +target_link_directories(ImageCropper + PRIVATE + ${OpenCV_LIB_DIR} +) +target_link_libraries(ImageCropper + PRIVATE + ${OpenCV_LIBS} +) + +target_include_directories(ImageCropper + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +set_target_properties(ImageCropper + PROPERTIES + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(ImageCropper + PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f3ecb35 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,42 @@ +{ + "version": 3, + "configurePresets": [ + { + "hidden": true, + "name": "Qt", + "cacheVariables": { + "CMAKE_PREFIX_PATH": "$env{QTDIR}" + } + }, + { + "hidden": true, + "name": "MSVC", + "inherits": "Qt", + "binaryDir": "build", + "cacheVariables": { + "CMAKE_GENERATOR": "Visual Studio 17 2022" + } + }, + { + "name": "MSVC-Release", + "inherits": "MSVC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "MSVC-Debug", + "inherits": "MSVC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "MSVC-RelWithDebInfo", + "inherits": "MSVC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + } + ] +} diff --git a/include/adb.h b/include/adb.h new file mode 100644 index 0000000..f213614 --- /dev/null +++ b/include/adb.h @@ -0,0 +1,7 @@ +#include +#include + +namespace adb { +bool is_connected(); +cv::Mat screenshot(); +} diff --git a/qt.cmake b/qt.cmake new file mode 100644 index 0000000..966f41c --- /dev/null +++ b/qt.cmake @@ -0,0 +1,17 @@ +if(QT_VERSION VERSION_LESS 6.3) + macro(qt_standard_project_setup) + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTORCC ON) + set(CMAKE_AUTOUIC ON) + endmacro() +endif() + +if(QT_VERSION VERSION_LESS 6.0) + macro(qt_add_executable name) + if(ANDROID) + add_library(name SHARED ${ARGN}) + else() + add_executable(${ARGV}) + endif() + endmacro() +endif() diff --git a/source/ImageCropper.cpp b/source/ImageCropper.cpp new file mode 100644 index 0000000..8d52c68 --- /dev/null +++ b/source/ImageCropper.cpp @@ -0,0 +1,195 @@ +#include "ImageCropper.h" +#include "QCropWidget.h" +#include "adb.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ImageCropper::ImageCropper(QWidget* parent) + : QMainWindow(parent) { + ui.setupUi(this); + + createActions(); + + QObject::connect(this, &ImageCropper::adbConnected, this, &ImageCropper::adbConnectedSlot); + + createUI(); + detectConnection(); +} + +ImageCropper::~ImageCropper() { +} + +void ImageCropper::createActions() { + connectionMenu = menuBar()->addMenu(tr("&Connection")); + connectAction = connectionMenu->addAction(tr("&Connect")); + + QObject::connect(connectAction, &QAction::triggered, this, &ImageCropper::connect); +} + +void ImageCropper::createUI() { + auto layout = new QHBoxLayout(); + + centralWidget()->setLayout(layout); + + imageWidget = new component::QCropWidget(); + QObject::connect( + imageWidget, + &component::QCropWidget::cropChanged, + this, + &ImageCropper::doCrop); + QObject::connect( + this, + &ImageCropper::screenshot, + imageWidget, + &component::QCropWidget::resetCrop); + layout->addWidget(imageWidget, 9); + + auto buttonsBox = new QGroupBox(tr("Actions")); + auto buttonsLayout = new QVBoxLayout(); + buttonsBox->setLayout(buttonsLayout); + + // buttonsBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + // buttonsBox->setMinimumWidth(100); + layout->addWidget(buttonsBox, 1); + + screencapButton = new QPushButton(tr("Screencap")); + screencapButton->setEnabled(false); // disable until connected + buttonsLayout->addWidget(screencapButton); + QObject::connect(screencapButton, &QPushButton::clicked, this, &ImageCropper::screenshot); + QObject::connect( + screencapButton, + &QPushButton::clicked, + imageWidget, + &component::QCropWidget::resetCrop); + + copyRoiButton = new QPushButton(tr("Copy ROI")); + copyRoiButton->setEnabled(false); // disable until roi is selected + buttonsLayout->addWidget(copyRoiButton, 1); + QObject::connect(copyRoiButton, &QPushButton::clicked, this, &ImageCropper::copyRoi); + + exportButton = new QPushButton(tr("Export")); + exportButton->setEnabled(false); // disable until roi is selected + buttonsLayout->addWidget(exportButton, 1); + QObject::connect(exportButton, &QPushButton::clicked, this, &ImageCropper::exportImage); + + cropSecondaryButton = new QCheckBox(tr("Secondary crop")); + cropSecondaryButton->setEnabled(false); // disable until roi is selected + buttonsLayout->addWidget(cropSecondaryButton); + QObject::connect( + cropSecondaryButton, + &QCheckBox::checkStateChanged, + imageWidget, + &component::QCropWidget::switchCrop); + + copyOffsetRoiButton = new QPushButton(tr("Copy offset ROI")); + copyOffsetRoiButton->setEnabled(false); // disable until roi is selected + buttonsLayout->addWidget(copyOffsetRoiButton, 1); + QObject::connect( + copyOffsetRoiButton, + &QPushButton::clicked, + this, + &ImageCropper::copyOffsetRoi); + + buttonsLayout->addStretch(); +} + +void ImageCropper::detectConnection() { + std::cout << "Detecting connection" << std::endl; + bool connected = adb::is_connected(); + if (connected) { + emit adbConnected(); + screencapButton->setEnabled(true); + } +} + +void ImageCropper::connect() { + bool ok; + QString address = QInputDialog::getText( + this, + tr("Connect to device"), + tr("Enter device address:"), + QLineEdit::Normal, + "", + &ok); + + if (ok && !address.isEmpty()) { + QProcess process; + process.start("adb connect " + address); + process.waitForFinished(); + detectConnection(); + } +} + +void ImageCropper::adbConnectedSlot() { + connectAction->setEnabled(false); + connectAction->setText(tr("Connected")); +} + +void ImageCropper::screenshot() { + image = adb::screenshot(); + imageWidget->setImage(image); + + copyRoiButton->setEnabled(false); + exportButton->setEnabled(false); + cropSecondaryButton->setEnabled(false); +} + +void ImageCropper::doCrop(const QRect& rect, bool secondary) { + // convert topleft to coordinates in the original image + if (secondary) { + imageWidget->offsetRoi(&offsetRoi.x, &offsetRoi.y, &offsetRoi.width, &offsetRoi.height); + } else { + imageWidget->selectedRoi(&roi.x, &roi.y, &roi.width, &roi.height); + } + + copyRoiButton->setEnabled(true); + exportButton->setEnabled(true); + cropSecondaryButton->setEnabled(true); + if (secondary) { + copyOffsetRoiButton->setEnabled(true); + } +} + +void ImageCropper::copyRoi() { + // copy the roi to the clipboard + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(tr("[%1, %2, %3, %4]").arg(roi.x).arg(roi.y).arg(roi.width).arg(roi.height)); +} + +void ImageCropper::copyOffsetRoi() { + // copy the offset roi to the clipboard + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(tr("[%1, %2, %3, %4]") + .arg(offsetRoi.x) + .arg(offsetRoi.y) + .arg(offsetRoi.width) + .arg(offsetRoi.height)); +} + +void ImageCropper::exportImage() { + auto filename = QFileDialog::getSaveFileName(this, tr("Save Image"), "", tr("Images (*.png)")); + + if (!filename.isEmpty()) { + auto imageWidth = image.cols; + auto imageHeight = image.rows; + + // scale roi back to original image size + auto x = roi.x * imageWidth / 1280; + auto y = roi.y * imageHeight / 720; + auto width = roi.width * imageWidth / 1280; + auto height = roi.height * imageHeight / 720; + + cv::Mat roi = image(cv::Rect(x, y, width, height)); + cv::imwrite(filename.toStdString(), roi); + } +} diff --git a/source/ImageCropper.h b/source/ImageCropper.h new file mode 100644 index 0000000..85a6789 --- /dev/null +++ b/source/ImageCropper.h @@ -0,0 +1,62 @@ +#pragma once + +#include "ui_ImageCropper.h" +#include +#include +#include +#include +#include +#include + +#include "QCropWidget.h" + +class ImageCropper : public QMainWindow { + Q_OBJECT + +public: + ImageCropper(QWidget* parent = nullptr); + ~ImageCropper(); + +private: + Ui::ImageCropperClass ui; + void createActions(); + void createUI(); + + void detectConnection(); + + QMenu* connectionMenu; + QAction* connectAction; + + component::QCropWidget* imageWidget; + + QPushButton* screencapButton; + QPushButton* copyRoiButton; + QPushButton* exportButton; + QCheckBox* cropSecondaryButton; + QPushButton* copyOffsetRoiButton; + + cv::Mat image; + + struct Roi { + int x; + int y; + int width; + int height; + }; + + Roi roi; + Roi offsetRoi; + +signals: + void adbConnected(); + +public slots: + void connect(); + void adbConnectedSlot(); + void screenshot(); + + void doCrop(const QRect& rect, bool secondary); + void copyRoi(); + void copyOffsetRoi(); + void exportImage(); +}; diff --git a/source/ImageCropper.ui b/source/ImageCropper.ui new file mode 100644 index 0000000..d6ca222 --- /dev/null +++ b/source/ImageCropper.ui @@ -0,0 +1,49 @@ + + + ImageCropperClass + + + + 0 + 0 + 658 + 360 + + + + ImageCropper + + + + true + + + + + TopToolBarArea + + + false + + + + + + + 0 + 0 + 658 + 33 + + + + + + Connect + + + + + + + diff --git a/source/QCropWidget.cpp b/source/QCropWidget.cpp new file mode 100644 index 0000000..e7a79fc --- /dev/null +++ b/source/QCropWidget.cpp @@ -0,0 +1,128 @@ +#include "QCropWidget.h" + +#include +#include +#include +#include + +#include + +component::QCropWidget::QCropWidget(QWidget* parent) + : QLabel(parent) { +} + +void component::QCropWidget::setImage(const cv::Mat& image) { + m_image = image; + QImage qimage(image.data, image.cols, image.rows, image.step, QImage::Format_RGB888); + qimage = qimage.scaled(size(), Qt::KeepAspectRatio); + setPixmap(QPixmap::fromImage(qimage)); + m_imageRect = QRect(0, 0, qimage.width(), qimage.height()); +} + +void component::QCropWidget::selectedRoi(int* x, int* y, int* width, int* height) const { + roiFromRect(m_cropRect, x, y, width, height); +} + +void component::QCropWidget::offsetRoi(int* x, int* y, int* width, int* height) const { + int x0, y0, w0, h0; + selectedRoi(&x0, &y0, &w0, &h0); + int x1, y1, w1, h1; + roiFromRect(m_secondaryCropRect, &x1, &y1, &w1, &h1); + *x = x1 - x0; + *y = y1 - y0; + *width = w1 - w0; + *height = h1 - h0; +} + +void component::QCropWidget::mousePressEvent(QMouseEvent* event) { + if (m_image.empty() || event->pos().x() >= m_imageRect.right()) { + return; + } + m_isCropping = true; + m_shouldDrawRect = true; + if (m_cropSecondary) { + m_secondaryCropRect.setTopLeft(event->pos()); + m_secondaryCropRect.setBottomRight(event->pos()); + } else { + m_cropRect.setTopLeft(event->pos()); + m_cropRect.setBottomRight(event->pos()); + } + update(); +} + +void component::QCropWidget::mouseMoveEvent(QMouseEvent* event) { + if (m_image.empty() || event->pos().x() >= m_imageRect.right()) { + return; + } + if (m_isCropping) { + if (m_cropSecondary) { + m_secondaryCropRect.setBottomRight(event->pos()); + } else { + m_cropRect.setBottomRight(event->pos()); + } + update(); + emit cropChanged(m_cropRect, m_cropSecondary); + } +} + +void component::QCropWidget::mouseReleaseEvent(QMouseEvent* event) { + if (m_image.empty()) { + return; + } + update(); + m_isCropping = false; + emit cropChanged(m_cropRect, m_cropSecondary); +} + +void component::QCropWidget::resizeEvent(QResizeEvent* event) { + QLabel::resizeEvent(event); + if (!m_image.empty()) { + setImage(m_image); + } + m_isCropping = false; + m_shouldDrawRect = false; + update(); +} + +void component::QCropWidget::paintEvent(QPaintEvent* event) { + QLabel::paintEvent(event); + if (m_shouldDrawRect) { + m_painter.begin(this); + m_painter.setPen(m_primaryPen); + m_painter.drawRect(m_cropRect); + + if (m_cropSecondary) { + m_painter.setPen(m_secondaryPen); + m_painter.drawRect(m_secondaryCropRect); + } + m_painter.end(); + } +} + +void component::QCropWidget::roiFromRect(const QRect& rect, int* x, int* y, int* width, int* height) + const { + *x = rect.topLeft().x() * 1280 / m_imageRect.width(); + *y = rect.topLeft().y() * 720 / m_imageRect.height(); + *width = rect.width() * 1280 / m_imageRect.width(); + *height = rect.height() * 720 / m_imageRect.height(); +} + +void component::QCropWidget::switchCrop(Qt::CheckState state) { + switch (state) { + case Qt::Unchecked: + m_cropSecondary = false; + break; + case Qt::PartiallyChecked: + case Qt::Checked: + m_cropSecondary = true; + break; + } + m_secondaryCropRect = QRect(); + update(); +} + +void component::QCropWidget::resetCrop() { + m_isCropping = false; + m_shouldDrawRect = false; + update(); +} diff --git a/source/QCropWidget.h b/source/QCropWidget.h new file mode 100644 index 0000000..9a4bd47 --- /dev/null +++ b/source/QCropWidget.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace component { +class QCropWidget : public QLabel { + Q_OBJECT + +public: + QCropWidget(QWidget* parent = nullptr); + + void setImage(const cv::Mat& image); + + void selectedRoi(int* x, int* y, int* width, int* height) const; + void offsetRoi(int* x, int* y, int* width, int* height) const; + +protected: + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + + void resizeEvent(QResizeEvent* event) override; + + void paintEvent(QPaintEvent* event) override; + +private: + bool m_isCropping = false; + bool m_shouldDrawRect = false; + bool m_cropSecondary = false; + QRect m_imageRect; + QRect m_cropRect; + QRect m_secondaryCropRect; + QPainter m_painter = QPainter(this); + QPen m_primaryPen = QPen(Qt::red, 2); + QPen m_secondaryPen = QPen(Qt::blue, 2); + cv::Mat m_image; + + void roiFromRect(const QRect& rect, int* x, int* y, int* width, int* height) const; + +signals: + void cropChanged(const QRect& rect, bool secondary); + +public slots: + void resetCrop(); + void switchCrop(Qt::CheckState state); +}; +} diff --git a/source/adb.cpp b/source/adb.cpp new file mode 100644 index 0000000..0696633 --- /dev/null +++ b/source/adb.cpp @@ -0,0 +1,76 @@ +#include "adb.h" +#include +#include +#include + +bool adb::is_connected() { + QProcess process; + QStringList args; + args << "devices"; + process.start("./adb", args); + process.waitForFinished(); + QString output = process.readAllStandardOutput(); + std::cout << output.toStdString() << std::endl; + // get what's after "List of devices attached" + int index = output.indexOf("List of devices attached"); + if (index == -1) { + return false; + } + output = output.mid(index + 24); + return output.contains("device"); +} + +cv::Mat adb::screenshot() { + QProcess process; + QStringList args; + args << "exec-out" + << "screencap" + << "-p"; + + process.start("./adb", args); + process.waitForFinished(); + QByteArray output = process.readAllStandardOutput(); + QBuffer buffer(&output); + buffer.open(QIODevice::ReadOnly); + QImageReader reader(&buffer); + QImage image = reader.read(); + + cv::Mat mat; + + switch (image.format()) { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: { + mat = cv::Mat( + image.height(), + image.width(), + CV_8UC4, + (void*)image.constBits(), + image.bytesPerLine()); + std::vector channels; + split(mat, channels); + channels.pop_back(); + cv::merge(channels, mat); + } break; + case QImage::Format_RGB888: + mat = cv::Mat( + image.height(), + image.width(), + CV_8UC3, + (void*)image.constBits(), + image.bytesPerLine()); + break; + case QImage::Format_Indexed8: + mat = cv::Mat( + image.height(), + image.width(), + CV_8UC1, + (void*)image.constBits(), + image.bytesPerLine()); + break; + } + + cv::cvtColor(mat, mat, CV_BGR2RGB); + + return mat; +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..3cc9602 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,10 @@ +#include "ImageCropper.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + ImageCropper w; + w.show(); + return a.exec(); +}