diff --git a/KShare.pro b/KShare.pro index d79073d..65d568e 100644 --- a/KShare.pro +++ b/KShare.pro @@ -62,7 +62,8 @@ SOURCES += main.cpp\ cropeditor/drawing/eraseritem.cpp \ cropeditor/drawing/rectitem.cpp \ cropeditor/drawing/ellipseitem.cpp \ - hotkeyinputdialog.cpp + hotkeyinputdialog.cpp \ + cropeditor/drawing/arrowitem.cpp HEADERS += mainwindow.hpp \ cropeditor/cropeditor.hpp \ @@ -103,7 +104,8 @@ HEADERS += mainwindow.hpp \ cropeditor/drawing/eraseritem.hpp \ cropeditor/drawing/rectitem.hpp \ cropeditor/drawing/ellipseitem.hpp \ - hotkeyinputdialog.hpp + hotkeyinputdialog.hpp \ + cropeditor/drawing/arrowitem.hpp LIBS += -lavcodec -lavformat -lavutil -lswscale -lavutil diff --git a/cropeditor/cropscene.cpp b/cropeditor/cropscene.cpp index 2dba538..3d45703 100644 --- a/cropeditor/cropscene.cpp +++ b/cropeditor/cropscene.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ CropScene::CropScene(QObject *parent, QPixmap *pixmap) addDrawingAction(menu, "Text", [] { return new TextItem; }); addDrawingAction(menu, "Rectangle", [] { return new RectItem; }); addDrawingAction(menu, "Ellipse", [] { return new EllipseItem; }); + addDrawingAction(menu, "Arrow", [] { return new ArrowItem; }); menu.addSeparator(); addDrawingAction(menu, "Eraser", [] { return new EraserItem; }); diff --git a/cropeditor/drawing/arrowitem.cpp b/cropeditor/drawing/arrowitem.cpp new file mode 100644 index 0000000..e1558e4 --- /dev/null +++ b/cropeditor/drawing/arrowitem.cpp @@ -0,0 +1,21 @@ +#include "arrowitem.hpp" +#include +#include + +void ArrowItem::mouseDragEvent(QGraphicsSceneMouseEvent *e, CropScene *scene) { + if (init.isNull()) { + init = e->scenePos(); + line = scene->addLine(QLineF(init, init), scene->pen()); + QPainterPath poly; + qreal w = settings::settings().value("arrow/width", 20).toDouble() / 2; + qreal h = settings::settings().value("arrow/height", 10).toDouble(); + poly.lineTo(QPoint(-w, h)); + poly.lineTo(QPoint(0, 0)); + poly.lineTo(QPoint(w, h)); + head = scene->addPath(poly, scene->pen(), scene->brush()); + } else { + line->setLine(QLineF(init, e->scenePos())); + head->setRotation(270 + qRadiansToDegrees(qAtan2((init.y() - e->scenePos().y()), (init.x() - e->scenePos().x())))); + } + head->setPos(e->scenePos()); +} diff --git a/cropeditor/drawing/arrowitem.hpp b/cropeditor/drawing/arrowitem.hpp new file mode 100644 index 0000000..13050f1 --- /dev/null +++ b/cropeditor/drawing/arrowitem.hpp @@ -0,0 +1,24 @@ +#ifndef ARROWITEM_HPP +#define ARROWITEM_HPP + +#include "drawitem.hpp" + + +class ArrowItem : public DrawItem { +public: + ArrowItem() { + } + QString name() override { + return "Arrow"; + } + void mouseDragEvent(QGraphicsSceneMouseEvent *e, CropScene *scene) override; + void mouseDragEndEvent(QGraphicsSceneMouseEvent *, CropScene *) override { + } + +private: + QGraphicsLineItem *line; + QGraphicsPathItem *head; + QPointF init; +}; + +#endif // ARROWITEM_HPP diff --git a/cropeditor/settings/brushpenselection.cpp b/cropeditor/settings/brushpenselection.cpp index fedb6fe..f2301ed 100644 --- a/cropeditor/settings/brushpenselection.cpp +++ b/cropeditor/settings/brushpenselection.cpp @@ -26,6 +26,9 @@ BrushPenSelection::BrushPenSelection(CropScene *scene) : QDialog(), ui(new Ui::B ui->brushStyle->setCurrentIndex(settings::settings().value("brushStyle", 1).toInt()); ui->pathItemHasBrush->setChecked(settings::settings().value("brushPath", false).toBool()); + ui->arroww->setValue(settings::settings().value("arrow/width", 20).toDouble()); + ui->arrowh->setValue(settings::settings().value("arrow/height", 10).toDouble()); + this->setFocus(); pen = scene->pen().color(); ui->penAlphaSlider->setValue(pen.alpha()); @@ -69,6 +72,8 @@ void BrushPenSelection::on_buttonBox_accepted() { settings::settings().setValue("blur/animatedHint", ui->animated->isChecked()); settings::settings().setValue("blur/qualityHint", ui->quality->isChecked()); settings::settings().setValue("blur/performanceHint", ui->performance->isChecked()); + settings::settings().setValue("arrow/width", ui->arroww->value()); + settings::settings().setValue("arrow/height", ui->arrowh->value()); close(); } diff --git a/cropeditor/settings/brushpenselection.ui b/cropeditor/settings/brushpenselection.ui index de5cebc..b949d86 100644 --- a/cropeditor/settings/brushpenselection.ui +++ b/cropeditor/settings/brushpenselection.ui @@ -6,8 +6,8 @@ 0 0 - 449 - 489 + 442 + 493 @@ -84,12 +84,115 @@ - + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + true + + + + + + + Blur settings + + + + + + Performance Hint + + + + + + + http://doc.qt.io/qt-5/qgraphicsblureffect.html#BlurHint-enum + + + <a href="http://doc.qt.io/qt-5/qgraphicsblureffect.html#BlurHint-enum">Blur Hints + + + true + + + Qt::TextBrowserInteraction + + + + + + + px + + + 30.000000000000000 + + + + + + + 3000 + + + Qt::Horizontal + + + + + + + Blur Radius + + + + + + + Animated Hint + + + + + + + Quality Hint + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Brush settings + + + + Brush alpha + + + @@ -169,10 +272,10 @@ - - + + - Path item has brush + Choose brush color @@ -193,113 +296,46 @@ - - + + - Choose brush color - - - - - - - Brush alpha + Path item has brush - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - true - - - - - + + - Blur settings + Arrow settings - - - - - http://doc.qt.io/qt-5/qgraphicsblureffect.html#BlurHint-enum - + + + - <a href="http://doc.qt.io/qt-5/qgraphicsblureffect.html#BlurHint-enum">Blur Hints - - - true - - - Qt::TextBrowserInteraction + Arrow width and height - - - - 3000 + + + + w: - - Qt::Horizontal - - - - - px - - 30.000000000000000 - - - - - Performance Hint + + + + h: - - - - - - Animated Hint - - - - - - - Quality Hint - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Blur Radius + + px diff --git a/recording/encoders/encoder.cpp.orig b/recording/encoders/encoder.cpp.orig new file mode 100644 index 0000000..481ba40 --- /dev/null +++ b/recording/encoders/encoder.cpp.orig @@ -0,0 +1,171 @@ +#include "encoder.hpp" + +#include +extern "C" { +#include +#include +} + +inline void throwAVErr(int ret, std::string section) { + char err[AV_ERROR_MAX_STRING_SIZE]; + av_make_error_string(err, AV_ERROR_MAX_STRING_SIZE, ret); + std::string newString(err); + throw std::runtime_error("Error during: " + section + ": " + newString); +} + +#define OR_DEF(s, e1, e2) s ? s->e1 : e2 + +Encoder::Encoder(QString &targetFile, QSize res, CodecSettings *settings) { + int ret; + // Format + ret = avformat_alloc_output_context2(&fc, NULL, NULL, targetFile.toLocal8Bit().constData()); + if (ret < 0) throwAVErr(ret, "format alloc"); + + // Stream + out->st = avformat_new_stream(fc, NULL); + if (!out->st) throw std::runtime_error("Unable to allocate video context"); + out->st->id = fc->nb_streams - 1; + if (fc->oformat->flags & AVFMT_GLOBALHEADER) fc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + av_dump_format(fc, 0, targetFile.toLocal8Bit().constData(), 1); + + // Codec + if (!fc->oformat->video_codec) throw std::runtime_error("Video codec not found"); + codec = avcodec_find_encoder(fc->oformat->video_codec); + if (!codec) throw std::runtime_error("Video codec not found"); + out->enc = avcodec_alloc_context3(codec); + if (!out->enc) throw std::runtime_error("Unable to allocate video context"); + + int fps = settings::settings().value("recording/framerate", 30).toInt(); + + out->enc->codec_id = codec->id; + out->enc->codec = codec; + + out->enc->bit_rate = OR_DEF(settings, bitrate, 400000); + out->enc->width = res.width() % 2 ? res.width() - 1 : res.width(); + out->enc->height = res.height() % 2 ? res.height() - 1 : res.height(); + size = QSize(out->enc->width, out->enc->height); + out->st->time_base = { 1, fps }; + out->enc->time_base = out->st->time_base; + + out->enc->gop_size = OR_DEF(settings, gopSize, 12); + out->enc->pix_fmt = AV_PIX_FMT_YUV420P; // blaze it + AVDictionary *dict = NULL; + if (out->enc->codec_id == AV_CODEC_ID_GIF) + out->enc->pix_fmt = AV_PIX_FMT_RGB8; + else if (out->enc->codec_id == AV_CODEC_ID_H264 || out->enc->codec_id == AV_CODEC_ID_H265) { + av_dict_set(&dict, "preset", settings ? settings->h264Profile.toLocal8Bit().constData() : "medium", 0); + av_dict_set_int(&dict, "crf", OR_DEF(settings, h264Crf, 12), 0); + } else if (out->enc->codec_id == AV_CODEC_ID_VP8 || out->enc->codec_id == AV_CODEC_ID_VP9) + av_dict_set_int(&dict, "lossless", OR_DEF(settings, vp9Lossless, false), 0); + + + ret = avcodec_open2(out->enc, codec, &dict); + av_dict_free(&dict); + if (ret < 0) throwAVErr(ret, "codec open"); + if (codec->capabilities & AV_CODEC_CAP_DR1) avcodec_align_dimensions(out->enc, &out->enc->width, &out->enc->height); + + ret = avcodec_parameters_from_context(out->st->codecpar, out->enc); + if (ret < 0) throwAVErr(ret, "stream opt copy"); + + // Frames + out->frame = av_frame_alloc(); + if (!out->frame) { + throw std::runtime_error("frame alloc"); + } + out->frame->format = out->enc->pix_fmt; + out->frame->width = out->enc->width; + out->frame->height = out->enc->height; + ret = av_frame_get_buffer(out->frame, 32); + if (ret < 0) throwAVErr(ret, "frame buffer alloc"); + + // Writer + ret = avio_open(&fc->pb, targetFile.toLocal8Bit().constData(), AVIO_FLAG_WRITE); + if (ret < 0) throwAVErr(ret, "writer open"); + ret = avformat_write_header(fc, NULL); + if (ret < 0) throwAVErr(ret, "write header"); + + success = true; +} + +void Encoder::setFrameRGB(QImage img) { + uint8_t *rgb = (uint8_t *)img.bits(); + int ret = av_frame_make_writable(out->frame); + if (ret < 0) throwAVErr(ret, "setFrameRGB"); + int lineSize[1] = { img.bytesPerLine() }; + out->sws = sws_getCachedContext(out->sws, out->enc->width, out->enc->height, AV_PIX_FMT_RGB24, out->enc->width, + out->enc->height, (AVPixelFormat)out->frame->format, 0, 0, 0, 0); + sws_scale(out->sws, (const uint8_t *const *)&rgb, lineSize, 0, out->enc->height, out->frame->data, out->frame->linesize); + out->frame->pts = out->nextPts++; +} + +Encoder::~Encoder() { + end(); +} + +bool Encoder::addFrame(QImage frm) { + if (!success) return false; + if (frm.size() != size) frm = frm.copy(QRect(QPoint(0, 0), size)); + if (frm.format() != QImage::Format_RGB888) frm = frm.convertToFormat(QImage::Format_RGB888); + setFrameRGB(frm); + AVPacket pkt; + pkt.size = 0; + pkt.data = NULL; + av_init_packet(&pkt); + int ret = avcodec_send_frame(out->enc, out->frame); + if (ret == AVERROR(EAGAIN)) { + do { + ret = avcodec_receive_packet(out->enc, &pkt); + if (ret < 0) { + if (ret != AVERROR(EAGAIN)) + throwAVErr(ret, "receive packet"); + else + break; + } + av_packet_rescale_ts(&pkt, out->enc->time_base, out->st->time_base); + pkt.stream_index = out->st->index; + ret = av_interleaved_write_frame(fc, &pkt); + } while (ret >= 0); + if (ret < 0 && ret != AVERROR(EAGAIN)) { + av_packet_unref(&pkt); + throwAVErr(ret, "send frame"); + } + } + av_packet_unref(&pkt); + if (ret < 0 && ret != AVERROR(EAGAIN)) throwAVErr(ret, "send frame"); + return true; +} + +bool Encoder::isRunning() { + return success; +} + +bool Encoder::end() { + if (ended) return false; + ended = true; + if (!success) { + goto cleanup; + } + avcodec_send_frame(out->enc, NULL); + int ret; + AVPacket pkt; + pkt.size = 0; + pkt.data = NULL; + av_init_packet(&pkt); + do { + ret = avcodec_receive_packet(out->enc, &pkt); + if (ret < 0) break; + av_packet_rescale_ts(&pkt, out->enc->time_base, out->st->time_base); + pkt.stream_index = out->st->index; + av_interleaved_write_frame(fc, &pkt); + } while (ret >= 0); + av_write_trailer(fc); +cleanup: + avcodec_free_context(&out->enc); + av_frame_free(&out->frame); + sws_freeContext(out->sws); + delete out; + if (!(fc->oformat->flags & AVFMT_NOFILE)) // + avio_closep(&fc->pb); + avformat_free_context(fc); + return success; +}