Implement recording [!tested] [prob. broken] [1AM]

Implement the basic barebone structure of GIF recording.
While it is likely broken (probably black frames and similar issues) it's a good start.
Good night.
This commit is contained in:
ArsenArsen 2017-06-06 01:26:26 +02:00
parent 080d5be40f
commit f2b2a7eb4a
14 changed files with 121 additions and 12 deletions

View File

@ -93,7 +93,7 @@ mac {
} else:win32 { } else:win32 {
SOURCES += $$PWD/platformspecifics/u32/u32backend.cpp SOURCES += $$PWD/platformspecifics/u32/u32backend.cpp
HEADERS += $$PWD/platformspecifics/u32/u32backend.hpp HEADERS += $$PWD/platformspecifics/u32/u32backend.hpp
LIBS += -luser32 LIBS += -luser32 -lkernel32 -lpthread
QT += winextras QT += winextras
} else:unix { } else:unix {
SOURCES += $$PWD/platformspecifics/x11/x11backend.cpp SOURCES += $$PWD/platformspecifics/x11/x11backend.cpp

View File

@ -18,6 +18,7 @@
#include <colorpicker/colorpickerscene.hpp> #include <colorpicker/colorpickerscene.hpp>
#include <functional> #include <functional>
#include <hotkeying.hpp> #include <hotkeying.hpp>
#include <recording/recordingformats.hpp>
#include <settings.hpp> #include <settings.hpp>
#include <uploaders/uploadersingleton.hpp> #include <uploaders/uploadersingleton.hpp>
@ -88,6 +89,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
addHotkeyItem("Area image", "area", [] { screenshotter::area(); }); addHotkeyItem("Area image", "area", [] { screenshotter::area(); });
addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); }); addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); });
addHotkeyItem("Stop Recording", "recordingstop", [&] { controller->end(); }); addHotkeyItem("Stop Recording", "recordingstop", [&] { controller->end(); });
addHotkeyItem("Start Recording", "recordingstart", [&] {
RecordingContext *ctx = new RecordingContext;
RecordingFormats *format = new RecordingFormats(RecordingFormats::GIF);
ctx->consumer = format->getConsumer();
ctx->finalizer = format->getFinalizer();
ctx->validator = format->getValidator();
ctx->format = format->getFormat();
controller->start(ctx);
});
ui->quickMode->setChecked(settings::settings().value("quickMode", false).toBool()); ui->quickMode->setChecked(settings::settings().value("quickMode", false).toBool());
ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool()); ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool());

View File

@ -7,3 +7,7 @@ QPixmap PlatformBackend::getCursor() {
// Some on how to do NSImage -> QPixmap: http://stackoverflow.com/a/2468961/3809164 // Some on how to do NSImage -> QPixmap: http://stackoverflow.com/a/2468961/3809164
// This is gonna be easier than with Windows // This is gonna be easier than with Windows
} }
pid_t PlatformBackend::pid() {
return getpid();
}

View File

@ -4,8 +4,9 @@
#include <QPixmap> #include <QPixmap>
class PlatformBackend { class PlatformBackend {
public: public:
QPixmap getCursor(); QPixmap getCursor();
pid_t pid();
static PlatformBackend &inst() { static PlatformBackend &inst() {
static PlatformBackend inst; static PlatformBackend inst;
return inst; return inst;

View File

@ -11,7 +11,8 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
if (cursorInfo.flags == CURSOR_SHOWING) { if (cursorInfo.flags == CURSOR_SHOWING) {
ICONINFO info; // It took me 5 hours to get to here ICONINFO info; // It took me 5 hours to get to here
if (GetIconInfo(cursorInfo.hCursor, &info)) { if (GetIconInfo(cursorInfo.hCursor, &info)) {
return std::tuple<QPoint, QPixmap>(QPoint(info.xHotspot, info.yHotspot), QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha)); return std::tuple<QPoint, QPixmap>(QPoint(info.xHotspot, info.yHotspot),
QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha));
} else } else
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap()); return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
} else } else
@ -19,3 +20,7 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
} else } else
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap()); return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
} }
DWORD pid() {
return GetCurrentProcessId();
}

View File

@ -2,10 +2,12 @@
#define U32BACKEND_HPP #define U32BACKEND_HPP
#include <QPixmap> #include <QPixmap>
#include <windows.h>
class PlatformBackend { class PlatformBackend {
public: public:
std::tuple<QPoint, QPixmap> getCursor(); std::tuple<QPoint, QPixmap> getCursor();
DWORD pid();
static PlatformBackend &inst() { static PlatformBackend &inst() {
static PlatformBackend inst; static PlatformBackend inst;
return inst; return inst;

View File

@ -2,6 +2,7 @@
#include <QPixmap> #include <QPixmap>
#include <QX11Info> #include <QX11Info>
#include <unistd.h>
#include <xcb/xcb_cursor.h> #include <xcb/xcb_cursor.h>
#include <xcb/xcb_util.h> #include <xcb/xcb_util.h>
#include <xcb/xfixes.h> #include <xcb/xfixes.h>
@ -22,3 +23,7 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
QPixmap::fromImage(QImage((quint8 *)pixels, cursorReply->width, cursorReply->height, QPixmap::fromImage(QImage((quint8 *)pixels, cursorReply->width, cursorReply->height,
QImage::Format_ARGB32_Premultiplied))); QImage::Format_ARGB32_Premultiplied)));
} }
pid_t PlatformBackend::pid() {
return getpid();
}

View File

@ -4,8 +4,9 @@
#include <QPixmap> #include <QPixmap>
class PlatformBackend { class PlatformBackend {
public: public:
std::tuple<QPoint, QPixmap> getCursor(); std::tuple<QPoint, QPixmap> getCursor();
pid_t pid();
static PlatformBackend &inst() { static PlatformBackend &inst() {
static PlatformBackend inst; static PlatformBackend inst;
return inst; return inst;

View File

@ -30,18 +30,33 @@ bool RecordingController::start(RecordingContext *context) {
bool RecordingController::end() { bool RecordingController::end() {
if (!isRunning()) return false; if (!isRunning()) return false;
timer.stop();
area = QRect(); area = QRect();
preview->close(); preview->close();
preview = 0; preview = 0;
UploaderSingleton::inst().upload(_context->finalizer()); WorkerContext *c = new WorkerContext;
c->consumer = [&](QImage) { queue(_context->finalizer()); };
c->targetFormat = QImage::Format_Alpha8;
c->pixmap = QPixmap(0, 0);
frame = 0; frame = 0;
time = 0; time = 0;
return true; return true;
} }
void RecordingController::queue(QByteArray arr) {
QMutexLocker l(&lock);
uploadQueue.enqueue(arr);
}
void RecordingController::timeout() { void RecordingController::timeout() {
if (isRunning()) { if (isRunning()) {
if (!_context->validator()) {
preview->close();
frame = 0;
time = 0;
preview = 0;
area = QRect();
}
time++; time++;
int localTime = time * timer.interval() - 3000; int localTime = time * timer.interval() - 3000;
if (localTime > 0) { if (localTime > 0) {
@ -59,6 +74,9 @@ void RecordingController::timeout() {
long second = localTime / 1000 % 60; long second = localTime / 1000 % 60;
long minute = localTime / 60000; long minute = localTime / 60000;
preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame); preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame);
} else {
QMutexLocker l(&lock);
UploaderSingleton::inst().upload(uploadQueue.dequeue());
} }
} }

View File

@ -5,6 +5,7 @@
#include <QFile> #include <QFile>
#include <QImage> #include <QImage>
#include <QMutex>
#include <QRect> #include <QRect>
#include <QTimer> #include <QTimer>
#include <functional> #include <functional>
@ -14,6 +15,7 @@ class RecordingContext {
public: public:
QImage::Format format; QImage::Format format;
std::function<void(QImage)> consumer; std::function<void(QImage)> consumer;
std::function<bool()> validator;
std::function<QByteArray()> finalizer; std::function<QByteArray()> finalizer;
}; };
@ -32,6 +34,8 @@ private slots:
void startWithArea(QRect newArea); void startWithArea(QRect newArea);
private: private:
QMutex lock;
QQueue<QByteArray> uploadQueue;
QRect area; QRect area;
RecordingContext *_context = 0; RecordingContext *_context = 0;
QTimer timer; QTimer timer;

View File

@ -1,12 +1,52 @@
#include "recordingformats.hpp" #include "recordingformats.hpp"
#include <QBuffer>
#include <QDateTime>
#include <QDir>
#include <QFile> #include <QFile>
#include <QStandardPaths>
#include <QTimer>
#include <gif-h/gif.h>
#include <platformbackend.hpp>
#include <settings.hpp>
#include <time.h>
RecordingFormats::RecordingFormats(RecordingFormats::Format f) { RecordingFormats::RecordingFormats(RecordingFormats::Format f) {
QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
if (path.isEmpty()) {
validator = [] { return false; };
return;
}
tmpDir = QDir(path);
QString name
= QString("KShareTemp-") + QString::number(PlatformBackend::inst().pid()) + "-" + QTime::currentTime().toString();
tmpDir.mkdir(name);
tmpDir.cd(name);
switch (f) { switch (f) {
case GIF: case GIF: {
// TODO iFormat = QImage::Format_Alpha8;
validator = [] { return true; };
consumer = [&](QImage img) { frames.push_back(img); };
finalizer = [&] {
if (frames.size() == 0) return QByteArray;
int f = 1;
uint32_t d = 1000 / settings::settings().value("recording/framerate", 30).toInt();
QImage startImg = frames[0];
GifWriter writer;
GifBegin(&writer, tmpDir.absoluteFilePath("resulting.gif"), startImg.width(), startImg.height(), d)
for (QImage &a : frames){ GifWriteFrame(writer, a.bits(), a.width(), a.height(), d) } QFile res(
tmpDir.absoluteFilePath("resulting.gif"));
if (!res.open(QFile::ReadOnly)) {
return QByteArray;
}
QByteArray data = res.readAll();
tmpDir.removeRecursively();
return data;
};
break; break;
}
default: default:
break; break;
} }
@ -20,7 +60,15 @@ std::function<QByteArray()> RecordingFormats::getFinalizer() {
return finalizer; return finalizer;
} }
QString RecordingFormats::getPrettyName(RecordingFormats::Format f) { std::function<bool()> RecordingFormats::getValidator() {
return validator;
}
QImage::Format RecordingFormats::getFormat() {
return iFormat;
}
QString RecordingFormats::getExt(RecordingFormats::Format f) {
switch (f) { switch (f) {
case None: case None:
return "None"; return "None";

View File

@ -1,6 +1,7 @@
#ifndef RECORDINGFORMATS_HPP #ifndef RECORDINGFORMATS_HPP
#define RECORDINGFORMATS_HPP #define RECORDINGFORMATS_HPP
#include <QDir>
#include <QFile> #include <QFile>
#include <QImage> #include <QImage>
#include <QString> #include <QString>
@ -8,16 +9,22 @@
class RecordingFormats { class RecordingFormats {
public: public:
enum Format { None, GIF }; enum Format { GIF, None };
RecordingFormats(Format f); RecordingFormats(Format f);
std::function<void(QImage)> getConsumer(); std::function<void(QImage)> getConsumer();
std::function<QByteArray()> getFinalizer(); std::function<QByteArray()> getFinalizer();
std::function<bool()> getValidator();
QImage::Format getFormat();
static QString getPrettyName(Format f); static QString getExt(Format f);
private: private:
std::function<void(QImage)> consumer; std::function<void(QImage)> consumer;
std::function<bool()> validator;
std::function<QByteArray()> finalizer; std::function<QByteArray()> finalizer;
QImage::Format iFormat;
QDir tmpDir;
int frame = 0;
}; };
#endif // RECORDINGFORMATS_HPP #endif // RECORDINGFORMATS_HPP

View File

@ -18,6 +18,7 @@ void Worker::queue(WorkerContext *context) {
c->image = context->pixmap.toImage(); c->image = context->pixmap.toImage();
c->consumer = context->consumer; c->consumer = context->consumer;
c->targetFormat = context->targetFormat; c->targetFormat = context->targetFormat;
c->underlyingThing = context;
inst->qqueue.enqueue(c); inst->qqueue.enqueue(c);
} }
@ -60,6 +61,8 @@ void Worker::process() {
if (!qqueue.isEmpty()) { if (!qqueue.isEmpty()) {
_WorkerContext *c = qqueue.dequeue(); _WorkerContext *c = qqueue.dequeue();
c->consumer(c->image.convertToFormat(c->targetFormat)); c->consumer(c->image.convertToFormat(c->targetFormat));
delete c->underlyingThing;
delete c;
} }
lock.unlock(); lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // STL likes it's scopes std::this_thread::sleep_for(std::chrono::milliseconds(10)); // STL likes it's scopes

View File

@ -19,6 +19,7 @@ struct _WorkerContext {
QImage image; QImage image;
QImage::Format targetFormat; QImage::Format targetFormat;
std::function<void(QImage)> consumer; std::function<void(QImage)> consumer;
WorkerContext *underlyingThing;
}; };
class Worker : public QObject { class Worker : public QObject {