25#include <QTextDocument>
27#include <QAbstractTextDocumentLayout>
34QtTextReader::QtTextReader() : width(1024), height(768), x_offset(0), y_offset(0), text(
""), font(QFont(
"Arial", 10)), text_color(
"#ffffff"), background_color(
"#000000"), is_open(false), gravity(
GRAVITY_CENTER)
41: width(width), height(height), x_offset(x_offset), y_offset(y_offset), text(text), font(font), text_color(text_color), background_color(background_color), is_open(false), gravity(gravity)
48 text_background_color = color;
58Json::Value QtTextReader::EvaluateStyle(int64_t frame)
const {
59 Json::Value result(Json::objectValue);
60 for (
const auto& key : text_animation.getMemberNames()) {
61 if (key ==
"color" || key ==
"background" || key ==
"stroke" || key ==
"border") {
62 Color color(
"#ffffff");
63 color.SetJsonValue(text_animation[key]);
64 auto rgba = color.GetColorRGBA(frame);
65 result[key] = QColor(rgba[0], rgba[1], rgba[2], rgba[3]).name(QColor::HexArgb).toStdString();
66 }
else if (key ==
"stroke_width" || key ==
"border_width" || key ==
"background_corner") {
69 result[key] = std::max(0.0, value.
GetValue(frame));
79double QtTextReader::RasterScale(
double& detail) {
90 const double max_scale = std::max(1.0, 8192.0 / std::max(width, height));
93 detail = std::max(1.0, std::min(clip_scale, max_scale / preview));
94 return preview * detail;
97 QSizeF size(width, height);
100 parent->scale ==
SCALE_STRETCH ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio);
101 const double fit = std::max(size.width() / width, size.height() / height);
102 return std::min(fit * clip_scale, max_scale);
106std::shared_ptr<QImage> QtTextReader::Render(
const Json::Value& style)
108 const auto color_at = [&](
const char* key,
const std::string& fallback) {
109 return style.isMember(key) ? style[key].asString() : fallback;
111 const auto number_at = [&](
const char* key,
double fallback) {
112 return style.isMember(key) ? style[key].asDouble() : fallback;
114 const auto text_color = color_at(
"color", this->text_color);
115 const auto background_color = color_at(
"background", this->background_color);
116 const auto stroke = color_at(
"stroke", this->stroke);
117 const auto border = color_at(
"border", this->border);
118 const auto stroke_width = number_at(
"stroke_width", this->stroke_width);
119 const auto border_width = number_at(
"border_width", this->border_width);
121 const double corner = std::min(std::max(0.0, number_at(
"background_corner", this->background_corner)),
122 std::min(width, height) / 2.0);
125 const int image_width = std::max(1,
int(std::round(width * render_scale)));
126 const int image_height = std::max(1,
int(std::round(height * render_scale)));
127 auto image = std::make_shared<QImage>(image_width, image_height, QImage::Format_RGBA8888_Premultiplied);
128 image->fill(corner > 0 ? QColor(Qt::transparent) : QColor(background_color.c_str()));
131 if (!painter.begin(image.get())) {
134 painter.scale(image_width /
double(width), image_height /
double(height));
137 painter.setRenderHint(QPainter::Antialiasing);
138 painter.setPen(Qt::NoPen);
139 painter.setBrush(QColor(background_color.c_str()));
140 painter.drawRoundedRect(QRectF(0, 0, width, height), corner, corner);
145 if (!text_background_color.empty()) {
146 painter.setBackgroundMode(Qt::OpaqueMode);
147 painter.setBackground(QBrush(text_background_color.c_str()));
151 painter.setPen(QPen(text_color.c_str()));
154 QFont text_font(font);
155 if (letter_spacing != 0.0)
156 text_font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing);
157 painter.setFont(text_font);
164 align_flag = Qt::AlignLeft | Qt::AlignTop;
167 align_flag = Qt::AlignHCenter | Qt::AlignTop;
170 align_flag = Qt::AlignRight | Qt::AlignTop;
173 align_flag = Qt::AlignVCenter | Qt::AlignLeft;
176 align_flag = Qt::AlignCenter;
179 align_flag = Qt::AlignVCenter | Qt::AlignRight;
182 align_flag = Qt::AlignLeft | Qt::AlignBottom;
185 align_flag = Qt::AlignHCenter | Qt::AlignBottom;
188 align_flag = Qt::AlignRight | Qt::AlignBottom;
193 if (document_layout) {
194 QTextDocument document;
195 document.setDefaultFont(text_font);
196 document.setDocumentMargin(background_padding);
198 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
199 option.setAlignment(Qt::Alignment(align_flag & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter)));
200 document.setDefaultTextOption(option);
201 document.setPlainText(QString::fromStdString(text));
202 document.setTextWidth(width);
203 QTextCursor cursor(&document);
204 cursor.select(QTextCursor::Document);
205 if (line_spacing != 1.0) {
206 QTextBlockFormat block;
207 block.setLineHeight(line_spacing * 100.0, QTextBlockFormat::ProportionalHeight);
208 cursor.mergeBlockFormat(block);
210 QAbstractTextDocumentLayout::PaintContext context;
211 context.palette.setColor(QPalette::Text, QColor(QString::fromStdString(text_color)));
213 painter.translate(x_offset, y_offset);
214 QTextCharFormat format;
215 if (stroke_width > 0) {
218 format.setForeground(Qt::transparent);
219 format.setTextOutline(QPen(QColor(QString::fromStdString(stroke)), stroke_width));
220 cursor.mergeCharFormat(format);
221 document.documentLayout()->draw(&painter, context);
222 format.setTextOutline(QPen(Qt::transparent, 0));
224 format.setForeground(QColor(QString::fromStdString(text_color)));
225 cursor.mergeCharFormat(format);
226 document.documentLayout()->draw(&painter, context);
228 if (border_width > 0) {
230 QPen border_pen(QColor(QString::fromStdString(border)), border_width);
231 border_pen.setJoinStyle(Qt::MiterJoin);
232 painter.setPen(border_pen);
233 painter.setBrush(Qt::NoBrush);
234 const QRectF border_rect(border_width / 2, border_width / 2,
235 width - border_width, height - border_width);
238 const double radius = std::max(0.0, corner - border_width / 2);
239 painter.setRenderHint(QPainter::Antialiasing);
240 painter.drawRoundedRect(border_rect, radius, radius);
242 painter.drawRect(border_rect);
246 painter.drawText(x_offset, y_offset, width, height, align_flag, text.c_str());
255void QtTextReader::UpdateInfo()
285void QtTextReader::UseStyle(
const Json::Value& style)
287 const std::string key = style.toStyledString();
288 for (
auto entry = render_cache.begin(); entry != render_cache.end(); ++entry) {
289 if (entry->first == key) {
290 render_cache.splice(render_cache.begin(), render_cache, entry);
291 image = entry->second;
292 rendered_style = style;
296 auto rendered = Render(style);
300 rendered_style = style;
301 render_cache.emplace_front(key, rendered);
304 const qint64 max_bytes = 16 * 1024 * 1024;
307 for (
auto entry = render_cache.begin(); entry != render_cache.end();) {
308 bytes += entry->second->sizeInBytes();
309 if (++count > 4 || (count > 1 && bytes > max_bytes))
310 entry = render_cache.erase(entry);
322 UseStyle(EvaluateStyle(render_frame));
345 render_cache.clear();
356 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
361 const double raster_scale = RasterScale(detail);
362 if (std::abs(raster_scale - render_scale) > 1e-6 || std::abs(detail - detail_scale) > 1e-6) {
363 render_scale = raster_scale;
364 detail_scale = detail;
365 render_frame = requested_frame;
369 if (is_open && !text_animation.empty()) {
370 const auto style = EvaluateStyle(requested_frame);
371 if (style != rendered_style)
378 auto image_frame = std::make_shared<Frame>(
379 requested_frame, image->size().width(), image->size().height(),
383 image_frame->AddImage(image);
389 auto image_frame = std::make_shared<Frame>(1, 640, 480, background_color, sample_count,
info.
channels);
408 root[
"type"] =
"QtTextReader";
409 root[
"document_layout"] = document_layout;
410 root[
"text_animation"] = text_animation;
411 root[
"background_padding"] = background_padding;
412 root[
"stroke_width"] = stroke_width;
413 root[
"stroke"] = stroke;
414 root[
"border_width"] = border_width;
415 root[
"border"] = border;
416 root[
"letter_spacing"] = letter_spacing;
417 root[
"line_spacing"] = line_spacing;
418 root[
"background_corner"] = background_corner;
419 root[
"width"] = width;
420 root[
"height"] = height;
421 root[
"x_offset"] = x_offset;
422 root[
"y_offset"] = y_offset;
424 root[
"font"] = font.toString().toStdString();
425 root[
"text_color"] = text_color;
426 root[
"background_color"] = background_color;
427 root[
"text_background_color"] = text_background_color;
428 root[
"gravity"] = gravity;
444 catch (
const std::exception& e)
447 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
458 if (!root[
"width"].isNull())
459 width = root[
"width"].asInt();
460 if (!root[
"height"].isNull())
461 height = root[
"height"].asInt();
462 if (!root[
"x_offset"].isNull())
463 x_offset = root[
"x_offset"].asInt();
464 if (!root[
"y_offset"].isNull())
465 y_offset = root[
"y_offset"].asInt();
466 if (!root[
"text"].isNull())
467 text = root[
"text"].asString();
468 if (!root[
"font"].isNull())
469 font.fromString(QString::fromStdString(root[
"font"].asString()));
470 if (!root[
"text_color"].isNull())
471 text_color = root[
"text_color"].asString();
472 if (!root[
"background_color"].isNull())
473 background_color = root[
"background_color"].asString();
474 if (!root[
"text_background_color"].isNull())
475 text_background_color = root[
"text_background_color"].asString();
476 if (!root[
"gravity"].isNull())
479 if (root.isMember(
"text_animation")) text_animation = root[
"text_animation"];
480 if (!root[
"document_layout"].isNull()) document_layout = root[
"document_layout"].asBool();
481 if (!root[
"background_padding"].isNull()) background_padding = std::max(0.0, root[
"background_padding"].asDouble());
482 if (!root[
"stroke_width"].isNull()) stroke_width = std::max(0.0, root[
"stroke_width"].asDouble());
483 if (!root[
"stroke"].isNull()) stroke = root[
"stroke"].asString();
484 if (!root[
"border_width"].isNull()) border_width = std::max(0.0, root[
"border_width"].asDouble());
485 if (!root[
"border"].isNull()) border = root[
"border"].asString();
486 if (!root[
"letter_spacing"].isNull()) letter_spacing = root[
"letter_spacing"].asDouble();
487 if (!root[
"line_spacing"].isNull()) line_spacing = std::max(0.1, root[
"line_spacing"].asDouble());
488 if (!root[
"background_corner"].isNull()) background_corner = std::max(0.0, root[
"background_corner"].asDouble());
489 width = std::max(1, width);
490 height = std::max(1, height);
491 if (root[
"fps"].isObject() && root[
"fps"][
"num"].asInt() > 0 && root[
"fps"][
"den"].asInt() > 0)
492 source_fps =
Fraction(root[
"fps"][
"num"].asInt(), root[
"fps"][
"den"].asInt());
493 render_cache.clear();
Header file for CacheBase class.
Header file for Clip class.
Header file for Color class.
Header file for all Exception classes.
Header file for Frame class.
Header file for QtTextReader class.
Header file for Timeline class.
This class represents a clip (used to arrange readers on the timeline)
openshot::Keyframe scale_x
Curve representing the horizontal scaling in percent (0 to 1)
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
openshot::ScaleType scale
This class represents a color (used on the timeline and clips)
double Y
The Y value of the coordinate (usually representing the value of the property being animated)
This class represents a fraction.
int num
Numerator for the fraction.
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
int den
Denominator for the fraction.
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Exception for invalid JSON.
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Point GetMaxPoint() const
Get max point (by Y coordinate)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Coordinate co
This is the primary coordinate.
void SetJson(const std::string value) override
Load JSON string into this object.
void SetTextBackgroundColor(std::string color)
void Close() override
Close Reader.
std::string Json() const override
Generate JSON string of this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
QtTextReader()
Default constructor (blank text)
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Json::Value JsonValue() const override
Generate Json::Value for this object.
void Open() override
Open Reader - which is called by the constructor automatically.
openshot::ReaderInfo info
Information about the current media file.
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This class represents a timeline.
This namespace is the default namespace for all code in the openshot library.
GravityType
This enumeration determines how clips are aligned to their parent container.
@ GRAVITY_TOP_LEFT
Align clip to the top left of its parent.
@ GRAVITY_LEFT
Align clip to the left of its parent (middle aligned)
@ GRAVITY_TOP_RIGHT
Align clip to the top right of its parent.
@ GRAVITY_RIGHT
Align clip to the right of its parent (middle aligned)
@ GRAVITY_BOTTOM_LEFT
Align clip to the bottom left of its parent.
@ GRAVITY_BOTTOM
Align clip to the bottom center of its parent.
@ GRAVITY_TOP
Align clip to the top center of its parent.
@ GRAVITY_BOTTOM_RIGHT
Align clip to the bottom right of its parent.
@ GRAVITY_CENTER
Align clip to the center of its parent (middle aligned)
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
@ SCALE_NONE
Do not scale the clip.
const Json::Value stringToJson(const std::string value)
bool has_single_image
Determines if this file only contains a single image.
float duration
Length of time (in seconds)
int width
The width of the video (in pixesl)
int channels
The number of audio channels used in the audio stream.
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
int height
The height of the video (in pixels)
int64_t video_length
The number of frames in the video stream.
std::string acodec
The name of the audio codec used to encode / decode the video stream.
std::string vcodec
The name of the video codec used to encode / decode the video stream.
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
bool has_video
Determines if this file has a video stream.
bool has_audio
Determines if this file has an audio stream.
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
int64_t file_size
Size of file (in bytes)