blur: merged box and gaussian blurs into one node

While these are two separate blur effects, merging them into one node will
allow quick swapping of blur methods.
This commit is contained in:
itsmattkc
2020-04-16 13:17:30 +10:00
parent 61654e8127
commit b4795c0222
8 changed files with 181 additions and 206 deletions
+2 -5
View File
@@ -47,8 +47,7 @@ void NodeFactory::Initialize()
library_.append(CreateInternal(static_cast<InternalID>(i)));
}
library_.append(new ExternalNode(":/shaders/gaussianblur.xml"));
library_.append(new ExternalNode(":/shaders/boxblur.xml"));
library_.append(new ExternalNode(":/shaders/blur.xml"));
library_.append(new ExternalNode(":/shaders/opacity.xml"));
library_.append(new ExternalNode(":/shaders/solid.xml"));
library_.append(new ExternalNode(":/shaders/stroke.xml"));
@@ -60,9 +59,7 @@ void NodeFactory::Initialize()
void NodeFactory::Destroy()
{
foreach (Node* n, library_) {
delete n;
}
qDeleteAll(library_);
library_.clear();
}
+108 -69
View File
@@ -22,6 +22,7 @@
#include <QFile>
#include "common/xmlutils.h"
#include "config/config.h"
#include "node.h"
@@ -37,11 +38,11 @@ NodeMetaReader::NodeMetaReader(const QString &xml_meta_filename) :
if (metadata_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&metadata_file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement() && reader.name() == "effect") {
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("effect")) {
XMLReadEffect(&reader);
} else {
reader.skipCurrentElement();
}
}
@@ -103,23 +104,42 @@ const QList<NodeInput *> &NodeMetaReader::inputs() const
void NodeMetaReader::Retranslate()
{
QMap<QString, QMap<QString, QString> >::const_iterator iterator;
{
// Re-translate every parameter name
QMap<QString, LanguageMap>::const_iterator iterator;
// Iterate through parameter language tables that we have
for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) {
NodeInput* this_input = GetInputWithID(iterator.key());
this_input->set_name(GetStringForCurrentLanguage(&iterator.value()));
// Iterate through parameter language tables that we have
for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) {
NodeInput* this_input = GetInputWithID(iterator.key());
this_input->set_name(GetStringForCurrentLanguage(&iterator.value()));
}
}
{
// Re-translate any combobox items
QMap<QString, QList<LanguageMap> >::const_iterator param_it;
for (param_it=combo_names_.begin(); param_it!=combo_names_.end(); param_it++) {
NodeInput* input = GetInputWithID(param_it.key());
QStringList combo_items;
foreach (const LanguageMap& lang_map, param_it.value()) {
combo_items.append(GetStringForCurrentLanguage(&lang_map));
}
input->set_combobox_strings(combo_items);
}
}
}
void NodeMetaReader::XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map)
void NodeMetaReader::XMLReadLanguageString(QXmlStreamReader* reader, LanguageMap* map)
{
QString lang;
// Traverse through name attributes for its language
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "lang") {
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("lang")) {
lang = attr.value().toString();
// We don't recognize any other "name" attributes at this time
@@ -134,9 +154,8 @@ void NodeMetaReader::XMLReadLanguageString(QXmlStreamReader* reader, QMap<QStrin
void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader)
{
// Traverse through effect attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
id_ = attr.value().toString();
// We don't recognize any other "effect" attributes at this time
@@ -150,32 +169,30 @@ void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader)
}
// Continue reading for other metadata
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "effect")) {
reader->readNext();
if (reader->isStartElement()) {
if (reader->name() == "name") {
// Pick up name
XMLReadLanguageString(reader, &names_);
} else if (reader->name() == "category") {
// Pick up category
XMLReadLanguageString(reader, &categories_);
} else if (reader->name() == "description") {
// Pick up description
XMLReadLanguageString(reader, &descriptions_);
} else if (reader->name() == "iterations") {
// Pick up iterations
XMLReadIterations(reader);
} else if (reader->name() == "fragment") {
// Pick up fragment shader code
XMLReadShader(reader, frag_code_);
} else if (reader->name() == "vertex") {
// Pick up vertex shader code
XMLReadShader(reader, vert_code_);
} else if (reader->name() == "param") {
// Pick up a parameter
XMLReadParam(reader);
}
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("name")) {
// Pick up name
XMLReadLanguageString(reader, &names_);
} else if (reader->name() == QStringLiteral("category")) {
// Pick up category
XMLReadLanguageString(reader, &categories_);
} else if (reader->name() == QStringLiteral("description")) {
// Pick up description
XMLReadLanguageString(reader, &descriptions_);
} else if (reader->name() == QStringLiteral("iterations")) {
// Pick up iterations
XMLReadIterations(reader);
} else if (reader->name() == QStringLiteral("fragment")) {
// Pick up fragment shader code
XMLReadShader(reader, frag_code_);
} else if (reader->name() == QStringLiteral("vertex")) {
// Pick up vertex shader code
XMLReadShader(reader, vert_code_);
} else if (reader->name() == QStringLiteral("param")) {
// Pick up a parameter
XMLReadParam(reader);
} else {
reader->skipCurrentElement();
}
}
}
@@ -199,13 +216,12 @@ void NodeMetaReader::XMLReadParam(QXmlStreamReader *reader)
bool is_iterative = false;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
param_id = attr.value().toString();
} else if (attr.name() == "type") {
} else if (attr.name() == QStringLiteral("type")) {
param_type = NodeParam::StringToDataType(attr.value().toString());
} else if (attr.name() == "iterative_input") {
} else if (attr.name() == QStringLiteral("iterative_input")) {
is_iterative = true;
}
}
@@ -217,32 +233,56 @@ void NodeMetaReader::XMLReadParam(QXmlStreamReader *reader)
QVariant default_val;
QHash<QString, QVariant> properties;
LanguageMap param_names;
QList<LanguageMap> combo_names;
QList<LanguageMap> combo_descriptions;
// Traverse through param contents for more data
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "param")) {
reader->readNext();
while (XMLReadNextStartElement(reader)) {
// NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will
// convert them automatically
if (reader->name() == QStringLiteral("name")) {
if (reader->isStartElement()) {
// NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will
// convert them automatically
if (reader->name() == "name") {
// Insert language into map
XMLReadLanguageString(reader, &param_names);
// See if we've already made a table for this param before
if (!param_names_.contains(param_id)) {
param_names_.insert(param_id, QMap<QString, QString>());
} else if (reader->name() == QStringLiteral("default")) {
// Reads the default value
default_val = NodeInput::StringToValue(param_type, reader->readElementText());
} else if (reader->name() == QStringLiteral("option")) {
// Read names and descriptions
LanguageMap names;
LanguageMap descriptions;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("name")) {
XMLReadLanguageString(reader, &names);
} else if (reader->name() == QStringLiteral("description")) {
XMLReadLanguageString(reader, &descriptions);
} else {
reader->skipCurrentElement();
}
// Insert language into table
XMLReadLanguageString(reader, &param_names_[param_id]);
} else if (reader->name() == "default") {
default_val = NodeInput::StringToValue(param_type, reader->readElementText());
} else {
properties.insert(reader->name().toString(), reader->readElementText());
}
combo_names.append(names);
combo_descriptions.append(descriptions);
} else {
properties.insert(reader->name().toString(), reader->readElementText());
}
}
param_names_.insert(param_id, param_names);
// Insert combo options if they exist
if (!combo_names.isEmpty()) {
combo_names_.insert(param_id, combo_names);
combo_descriptions_.insert(param_id, combo_descriptions);
}
NodeInput* input = new NodeInput(param_id, param_type, default_val);
QHash<QString, QVariant>::const_iterator iterator;
@@ -263,9 +303,8 @@ void NodeMetaReader::XMLReadShader(QXmlStreamReader *reader, QString &destinatio
QString code_url;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "url") {
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("url")) {
code_url = attr.value().toString();
// We don't recognize any other "shader" attributes at this time
@@ -285,7 +324,7 @@ void NodeMetaReader::XMLReadShader(QXmlStreamReader *reader, QString &destinatio
}
}
QString NodeMetaReader::GetStringForCurrentLanguage(const QMap<QString, QString> *language_map)
QString NodeMetaReader::GetStringForCurrentLanguage(const LanguageMap *language_map)
{
if (language_map->isEmpty()) {
// There are no entries for this map, this must be an empty string
@@ -293,7 +332,7 @@ QString NodeMetaReader::GetStringForCurrentLanguage(const QMap<QString, QString>
}
// Get current language config
QString language = Config::Current()["Language"].toString();
QString language = Config::Current()[QStringLiteral("Language")].toString();
// See if our map has an exact language match
QString str_for_lang = language_map->value(language);
+10 -6
View File
@@ -52,22 +52,26 @@ public:
void Retranslate();
private:
void XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map);
using LanguageMap = QMap<QString, QString>;
void XMLReadLanguageString(QXmlStreamReader* reader, LanguageMap *map);
void XMLReadEffect(QXmlStreamReader *reader);
void XMLReadIterations(QXmlStreamReader* reader);
void XMLReadParam(QXmlStreamReader* reader);
void XMLReadShader(QXmlStreamReader* reader, QString& destination);
static QString GetStringForCurrentLanguage(const QMap<QString, QString> *language_map);
static QString GetStringForCurrentLanguage(const LanguageMap *language_map);
NodeInput* GetInputWithID(const QString& id) const;
QString xml_filename_;
QMap<QString, QString> names_;
QMap<QString, QString> descriptions_;
QMap<QString, QString> categories_;
QMap<QString, QMap<QString, QString> > param_names_;
LanguageMap names_;
LanguageMap descriptions_;
LanguageMap categories_;
QMap<QString, LanguageMap > param_names_;
QMap<QString, QList<LanguageMap> > combo_names_;
QMap<QString, QList<LanguageMap> > combo_descriptions_;
QString id_;
@@ -1,13 +1,12 @@
#version 110
// Standard inputs
uniform vec2 ove_resolution;
varying vec2 ove_texcoord;
uniform int ove_iteration;
// Node parameter inputs
uniform sampler2D tex_in;
uniform float sigma_in;
uniform int method_in;
uniform float radius_in;
uniform bool horiz_in;
uniform bool vert_in;
uniform bool repeat_edge_pixels_in;
@@ -15,6 +14,10 @@ uniform bool repeat_edge_pixels_in;
// Gaussian function uses PI
#define M_PI 3.1415926535897932384626433832795
// Methods
#define METHOD_BOX_BLUR 0
#define METHOD_GAUSSIAN_BLUR 1
// Single gaussian formula (unused, mainly here for documentation/just in case)
//float gaussian(float x, float sigma) {
// return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0));
@@ -27,35 +30,49 @@ float gaussian2(float x, float y, float sigma) {
}
void main(void) {
// Determine this iteration should process anything or not
if (sigma_in == 0.0 // If the radius is zero
|| (ove_iteration == 0 && !horiz_in) // Or this iteration (horizontal) is disabled
|| (ove_iteration == 1 && !vert_in)) { // Or this iteration (vertical) is disabled
// No-op, do nothing
if (radius_in == 0.0
|| (ove_iteration == 0 && !horiz_in)
|| (ove_iteration == 1 && !vert_in)) {
gl_FragColor = texture2D(tex_in, ove_texcoord);
return;
}
// We only sample on hard pixels, so we don't accept decimal radii
float real_radius = ceil(radius_in);
// Using (3 * sigma) because 3 standard deviations covers 97% of the blur according to this document:
// http://chemaguerra.com/gaussian-filter-radius/
float radius = ceil(3.0 * sigma_in);
vec4 composite = vec4(0.0);
float divider, sigma;
if (method_in == METHOD_BOX_BLUR) {
// Calculate the weight of each pixel based on the radius
divider = 1.0 / real_radius;
} else if (method_in == METHOD_GAUSSIAN_BLUR) {
// Using (radius = 3 * sigma) because 3 standard deviations covers 97% of the blur according to this document:
// http://chemaguerra.com/gaussian-filter-radius/
sigma = real_radius;
real_radius *= 3.0;
// Use gaussian formula to calculate the weight of all pixels
divider = 0.0;
for (float i=-real_radius+0.5;i<=real_radius;i+=2.0) {
divider += gaussian2(i, 0.0, sigma);
}
// Use gaussian formula to calculate the weight of all pixels
float sum = 0.0;
for (float i=-radius+0.5;i<=radius;i+=2.0) {
sum += gaussian2(i, 0.0, sigma_in);
}
// We can take advantage of OpenGL's linear interpolation by sampling between pixels. This produces a mathematically
// identical result while allowing us to sample the texture half as many times.
vec4 composite = vec4(0.0);
for (float i=-radius+0.5;i<=radius;i+=2.0) {
// Calculate weight of this pixel using gaussian
float weight = (gaussian2(i, 0.0, sigma_in)/sum);
for (float i=-real_radius+0.5;i<=real_radius;i+=2.0) {
float weight;
if (method_in == METHOD_BOX_BLUR) {
weight = divider;
} else if (method_in == METHOD_GAUSSIAN_BLUR) {
weight = gaussian2(i, 0.0, sigma)/divider;
}
// Determine pixel coordinate based on iteration
// We use two iterations horizontally and vertically since that produces a mathematically identical result and
// (2*radius) is much faster than (radius^2)
vec2 pixel_coord = ove_texcoord;
if (ove_iteration == 0) {
pixel_coord.x += i/ove_resolution.x;
@@ -71,5 +88,6 @@ void main(void) {
composite += texture2D(tex_in, pixel_coord) * weight;
}
}
gl_FragColor = composite;
}
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect id="org.olivevideoeditor.Olive.boxblur">
<effect id="org.olivevideoeditor.Olive.blur">
<!-- Effect Name -->
<name lang="en_US">Box Blur</name>
<name lang="en_US">Blur</name>
<!-- Effect Category -->
<category lang="en_US">Blur</category>
<!-- Effect Description -->
<description lang="en_US">
A fast blur calculated by averaging a square of pixels around each pixel. Fast, but can have a "striated" look to it.
Blurs an image.
</description>
<!-- Parameter: Texture Input -->
@@ -16,6 +16,19 @@
<name lang="en_US">Input</name>
</param>
<!-- Parameter: Method Input -->
<param id="method_in" type="combo">
<name lang="en_US">Method</name>
<option>
<name lang="en_US">Box</name>
<description lang="en_US">A fast blur calculated by averaging a square of pixels around each pixel. Fast, but can have a "striated" look to it.</description>
</option>
<option>
<name lang="en_US">Gaussian</name>
<description lang="en_US">A smooth blur using the gaussian function. Slower than box blur but much prettier.</description>
</option>
</param>
<!-- Parameter: Radius Input -->
<param id="radius_in" type="float">
<name lang="en_US">Radius</name>
@@ -42,7 +55,7 @@
</param>
<!-- Qt Resource path to fragment shader -->
<fragment url=":/shaders/boxblur.frag"/>
<fragment url=":/shaders/blur.frag"/>
<!-- Blur uses two iterations for horizontal and vertical since calculating 2*radius is faster than radius^2 -->
<iterations>2</iterations>
-45
View File
@@ -1,45 +0,0 @@
#version 110
uniform vec2 ove_resolution;
varying vec2 ove_texcoord;
uniform int ove_iteration;
uniform sampler2D tex_in;
uniform float radius_in;
uniform bool horiz_in;
uniform bool vert_in;
uniform bool repeat_edge_pixels_in;
void main(void) {
if (radius_in == 0.0
|| (ove_iteration == 0 && !horiz_in)
|| (ove_iteration == 1 && !vert_in)) {
gl_FragColor = texture2D(tex_in, ove_texcoord);
return;
}
// We only sample on hard pixels, so we don't accept decimal radii
float real_radius = ceil(radius_in);
// Calculate the weight of each pixel based on the radius
float divider = 1.0 / real_radius;
vec4 composite = vec4(0.0);
for (float i=-real_radius+0.5;i<=real_radius;i+=2.0) {
vec2 pixel_coord = ove_texcoord;
if (ove_iteration == 0) {
pixel_coord.x += i/ove_resolution.x;
} else if (ove_iteration == 1) {
pixel_coord.y += i/ove_resolution.y;
}
if (repeat_edge_pixels_in
|| (pixel_coord.x >= 0.0
&& pixel_coord.x < 1.0
&& pixel_coord.y >= 0.0
&& pixel_coord.y < 1.0)) {
composite += texture2D(tex_in, pixel_coord) * divider;
}
}
gl_FragColor = composite;
}
-49
View File
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect id="org.olivevideoeditor.Olive.gaussianblur">
<!-- Effect Name -->
<name lang="en_US">Gaussian Blur</name>
<!-- Effect Category -->
<category lang="en_US">Blur</category>
<!-- Effect Description -->
<description lang="en_US">
A smooth blur using the gaussian function. Slower than box blur but also prettier.
</description>
<!-- Parameter: Texture Input -->
<param id="tex_in" type="texture" iterative_input="1">
<name lang="en_US">Input</name>
</param>
<!-- Parameter: Sigma Value -->
<param id="sigma_in" type="float">
<name lang="en_US">Sigma</name>
<min>0</min>
<default>5.5</default>
</param>
<!-- Parameter: Horizontal Enable -->
<param id="horiz_in" type="bool">
<name lang="en_US">Horizontal</name>
<default>1</default>
</param>
<!-- Parameter: Vertical Enable -->
<param id="vert_in" type="bool">
<name lang="en_US">Vertical</name>
<default>1</default>
</param>
<!-- Parameter: Repeat Edge Pixels -->
<param id="repeat_edge_pixels_in" type="bool">
<name lang="en_US">Repeat Edge Pixels</name>
<default>0</default>
</param>
<!-- Qt Resource path to fragment shader -->
<fragment url=":/shaders/gaussianblur.frag" />
<!-- Blur uses two iterations for horizontal and vertical since calculating 2*radius is faster than radius^2 -->
<iterations>2</iterations>
</effect>
+2 -4
View File
@@ -2,8 +2,8 @@
<qresource prefix="/shaders">
<file>alphaover.frag</file>
<file>alphaover.xml</file>
<file>boxblur.frag</file>
<file>boxblur.xml</file>
<file>blur.frag</file>
<file>blur.xml</file>
<file>colorgradient.frag</file>
<file>colorwheel.frag</file>
<file>crossdissolve.frag</file>
@@ -12,8 +12,6 @@
<file>dropshadow.xml</file>
<file>diptoblack.frag</file>
<file>diptoblack.xml</file>
<file>gaussianblur.frag</file>
<file>gaussianblur.xml</file>
<file>opacity.frag</file>
<file>opacity.xml</file>
<file>solid.frag</file>