diff --git a/app/node/factory.cpp b/app/node/factory.cpp index e70b9db63..91f3e854f 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -47,8 +47,7 @@ void NodeFactory::Initialize() library_.append(CreateInternal(static_cast(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(); } diff --git a/app/node/metareader.cpp b/app/node/metareader.cpp index 39a420687..41e9dec09 100644 --- a/app/node/metareader.cpp +++ b/app/node/metareader.cpp @@ -22,6 +22,7 @@ #include +#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 &NodeMetaReader::inputs() const void NodeMetaReader::Retranslate() { - QMap >::const_iterator iterator; + { + // Re-translate every parameter name + QMap::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 >::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* 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, QMapattributes(); - 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 properties; + LanguageMap param_names; + QList combo_names; + QList 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, ¶m_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()); + } 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, ¶m_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::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 *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 } // 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); diff --git a/app/node/metareader.h b/app/node/metareader.h index 4806da78b..dda052c83 100644 --- a/app/node/metareader.h +++ b/app/node/metareader.h @@ -52,22 +52,26 @@ public: void Retranslate(); private: - void XMLReadLanguageString(QXmlStreamReader* reader, QMap* map); + using LanguageMap = QMap; + + 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 *language_map); + static QString GetStringForCurrentLanguage(const LanguageMap *language_map); NodeInput* GetInputWithID(const QString& id) const; QString xml_filename_; - QMap names_; - QMap descriptions_; - QMap categories_; - QMap > param_names_; + LanguageMap names_; + LanguageMap descriptions_; + LanguageMap categories_; + QMap param_names_; + QMap > combo_names_; + QMap > combo_descriptions_; QString id_; diff --git a/app/shaders/gaussianblur.frag b/app/shaders/blur.frag similarity index 50% rename from app/shaders/gaussianblur.frag rename to app/shaders/blur.frag index 3da27b07f..796f7cad4 100644 --- a/app/shaders/gaussianblur.frag +++ b/app/shaders/blur.frag @@ -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; } diff --git a/app/shaders/boxblur.xml b/app/shaders/blur.xml similarity index 63% rename from app/shaders/boxblur.xml rename to app/shaders/blur.xml index 2a54a48d2..320d23395 100644 --- a/app/shaders/boxblur.xml +++ b/app/shaders/blur.xml @@ -1,14 +1,14 @@ - + - Box Blur + Blur Blur - 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. @@ -16,6 +16,19 @@ Input + + + Method + + + + Radius @@ -42,7 +55,7 @@ - + 2 diff --git a/app/shaders/boxblur.frag b/app/shaders/boxblur.frag deleted file mode 100644 index f384ad526..000000000 --- a/app/shaders/boxblur.frag +++ /dev/null @@ -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; -} diff --git a/app/shaders/gaussianblur.xml b/app/shaders/gaussianblur.xml deleted file mode 100644 index 205a22e06..000000000 --- a/app/shaders/gaussianblur.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - Gaussian Blur - - - Blur - - - - A smooth blur using the gaussian function. Slower than box blur but also prettier. - - - - - Input - - - - - Sigma - 0 - 5.5 - - - - - Horizontal - 1 - - - - - Vertical - 1 - - - - - Repeat Edge Pixels - 0 - - - - - - - 2 - diff --git a/app/shaders/shaders.qrc b/app/shaders/shaders.qrc index 9715568ec..bdbb81adb 100644 --- a/app/shaders/shaders.qrc +++ b/app/shaders/shaders.qrc @@ -2,8 +2,8 @@ alphaover.frag alphaover.xml - boxblur.frag - boxblur.xml + blur.frag + blur.xml colorgradient.frag colorwheel.frag crossdissolve.frag @@ -12,8 +12,6 @@ dropshadow.xml diptoblack.frag diptoblack.xml - gaussianblur.frag - gaussianblur.xml opacity.frag opacity.xml solid.frag