This is overkill
This commit is contained in:
parent
2798d4bf66
commit
51a9de3af3
@ -147,6 +147,10 @@ bool AssetPath::operator==(AssetPath const& rhs) const {
|
|||||||
return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives);
|
return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetPath::AssetPath(String const& path) {
|
||||||
|
*this = move(AssetPath::split(path)); // split code should probably be in here, but whatever
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
|
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
|
||||||
os << rhs.basePath;
|
os << rhs.basePath;
|
||||||
if (rhs.subPath) {
|
if (rhs.subPath) {
|
||||||
|
@ -51,6 +51,9 @@ struct AssetPath {
|
|||||||
// sourcePath.
|
// sourcePath.
|
||||||
static String relativeTo(String const& sourcePath, String const& givenPath);
|
static String relativeTo(String const& sourcePath, String const& givenPath);
|
||||||
|
|
||||||
|
AssetPath() = default;
|
||||||
|
AssetPath(String const& path);
|
||||||
|
|
||||||
String basePath;
|
String basePath;
|
||||||
Maybe<String> subPath;
|
Maybe<String> subPath;
|
||||||
NestedDirectives directives;
|
NestedDirectives directives;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#include "StarImage.hpp"
|
#include "StarImage.hpp"
|
||||||
#include "StarImageProcessing.hpp"
|
#include "StarImageProcessing.hpp"
|
||||||
#include "StarDirectives.hpp"
|
#include "StarDirectives.hpp"
|
||||||
|
#include "StarXXHash.hpp"
|
||||||
|
#include "StarHash.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -16,22 +18,32 @@ NestedDirectives::NestedDirectives(String&& directives) {
|
|||||||
void NestedDirectives::parseDirectivesIntoLeaf(String const& directives) {
|
void NestedDirectives::parseDirectivesIntoLeaf(String const& directives) {
|
||||||
Leaf leaf;
|
Leaf leaf;
|
||||||
for (String& op : directives.split('?')) {
|
for (String& op : directives.split('?')) {
|
||||||
if (!op.empty()) {
|
if (!op.empty())
|
||||||
leaf.operations.append(imageOperationFromString(op));
|
leaf.entries.emplace_back(imageOperationFromString(op), op);
|
||||||
leaf.strings.append(move(op));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m_root = std::make_shared<Cell>(move(leaf));
|
m_root = std::make_shared<Cell>(move(leaf));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NestedDirectives::empty() const {
|
inline bool NestedDirectives::empty() const {
|
||||||
return (bool)m_root;
|
return (bool)m_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NestedDirectives::append(const NestedDirectives& other) {
|
inline bool NestedDirectives::compare(NestedDirectives const& other) const {
|
||||||
|
if (m_root == other.m_root)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NestedDirectives::append(NestedDirectives const& other) {
|
||||||
convertToBranches().emplace_back(other.branch());
|
convertToBranches().emplace_back(other.branch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NestedDirectives& NestedDirectives::operator+=(NestedDirectives const& other) {
|
||||||
|
append(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
String NestedDirectives::toString() const {
|
String NestedDirectives::toString() const {
|
||||||
String string;
|
String string;
|
||||||
addToString(string);
|
addToString(string);
|
||||||
@ -51,9 +63,8 @@ void NestedDirectives::forEach(LeafCallback callback) const {
|
|||||||
void NestedDirectives::forEachPair(LeafPairCallback callback) const {
|
void NestedDirectives::forEachPair(LeafPairCallback callback) const {
|
||||||
if (m_root) {
|
if (m_root) {
|
||||||
LeafCallback pairCallback = [&](Leaf const& leaf) {
|
LeafCallback pairCallback = [&](Leaf const& leaf) {
|
||||||
size_t length = leaf.length();
|
for (auto& entry : leaf.entries)
|
||||||
for (size_t i = 0; i != length; ++i)
|
callback(entry.operation, entry.string);
|
||||||
callback(leaf.operations.at(i), leaf.strings.at(i));
|
|
||||||
};
|
};
|
||||||
m_root->forEach(pairCallback);
|
m_root->forEach(pairCallback);
|
||||||
}
|
}
|
||||||
@ -71,9 +82,8 @@ bool NestedDirectives::forEachPairAbortable(AbortableLeafPairCallback callback)
|
|||||||
return false;
|
return false;
|
||||||
else {
|
else {
|
||||||
AbortableLeafCallback pairCallback = [&](Leaf const& leaf) -> bool {
|
AbortableLeafCallback pairCallback = [&](Leaf const& leaf) -> bool {
|
||||||
size_t length = leaf.length();
|
for (auto& entry : leaf.entries) {
|
||||||
for (size_t i = 0; i != length; ++i) {
|
if (!callback(entry.operation, entry.string))
|
||||||
if (!callback(leaf.operations.at(i), leaf.strings.at(i)))
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,8 +95,8 @@ bool NestedDirectives::forEachPairAbortable(AbortableLeafPairCallback callback)
|
|||||||
|
|
||||||
Image NestedDirectives::apply(Image& image) const {
|
Image NestedDirectives::apply(Image& image) const {
|
||||||
Image current = image;
|
Image current = image;
|
||||||
forEach([&](Leaf const& leaf) {
|
forEachPair([&](ImageOperation const& operation, String const& string) {
|
||||||
current = processImageOperations(leaf.operations, current);
|
processImageOperation(operation, current);
|
||||||
});
|
});
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
@ -105,11 +115,33 @@ NestedDirectives::Branches& NestedDirectives::convertToBranches() {
|
|||||||
return m_root->value.get<Branches>();
|
return m_root->value.get<Branches>();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t NestedDirectives::Leaf::length() const {
|
bool NestedDirectives::Leaf::Entry::operator==(NestedDirectives::Leaf::Entry const& other) const {
|
||||||
if (operations.size() != strings.size())
|
return string == other.string;
|
||||||
throw DirectivesException("NestedDirectives leaf has mismatching operation/string List sizes");
|
}
|
||||||
|
|
||||||
return operations.size();
|
bool NestedDirectives::Leaf::Entry::operator!=(NestedDirectives::Leaf::Entry const& other) const {
|
||||||
|
return string != other.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t NestedDirectives::Leaf::length() const {
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NestedDirectives::Leaf::operator==(NestedDirectives::Leaf const& other) const {
|
||||||
|
size_t len = length();
|
||||||
|
if (len != other.length())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i != len; ++i) {
|
||||||
|
if (entries[i] != other.entries[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NestedDirectives::Leaf::operator!=(NestedDirectives::Leaf const& other) const {
|
||||||
|
return !(*this == other);
|
||||||
}
|
}
|
||||||
|
|
||||||
NestedDirectives::Cell::Cell() : value(Leaf()) {};
|
NestedDirectives::Cell::Cell() : value(Leaf()) {};
|
||||||
@ -118,11 +150,33 @@ NestedDirectives::Cell::Cell(Branches&& branches) : value(move(branches)) {};
|
|||||||
NestedDirectives::Cell::Cell(const Leaf& leaf) : value(leaf) {};
|
NestedDirectives::Cell::Cell(const Leaf& leaf) : value(leaf) {};
|
||||||
NestedDirectives::Cell::Cell(const Branches& branches) : value(branches) {};
|
NestedDirectives::Cell::Cell(const Branches& branches) : value(branches) {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
bool NestedDirectives::Cell::operator==(NestedDirectives::Cell const& other) const {
|
||||||
|
if (auto leaf = value.ptr<Leaf>()) {
|
||||||
|
if (auto otherLeaf = other.value.ptr<Leaf>())
|
||||||
|
return *leaf == *otherLeaf;
|
||||||
|
else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (auto& branch : value.get<Branches>()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool NestedDirectives::Cell::operator!=(NestedDirectives::Cell const& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
//*/
|
||||||
|
|
||||||
void NestedDirectives::Cell::buildString(String& string) const {
|
void NestedDirectives::Cell::buildString(String& string) const {
|
||||||
if (auto leaf = value.ptr<Leaf>())
|
if (auto leaf = value.ptr<Leaf>())
|
||||||
for (auto& leafString : leaf->strings) {
|
for (auto& entry : leaf->entries) {
|
||||||
string += "?";
|
string += "?";
|
||||||
string += leafString;
|
string += entry.string;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (auto& branch : value.get<Branches>())
|
for (auto& branch : value.get<Branches>())
|
||||||
|
@ -12,10 +12,19 @@ STAR_EXCEPTION(DirectivesException, StarException);
|
|||||||
class NestedDirectives {
|
class NestedDirectives {
|
||||||
public:
|
public:
|
||||||
struct Leaf {
|
struct Leaf {
|
||||||
List<ImageOperation> operations;
|
struct Entry {
|
||||||
List<String> strings;
|
ImageOperation operation;
|
||||||
|
String string;
|
||||||
|
|
||||||
|
bool operator==(Entry const& other) const;
|
||||||
|
bool operator!=(Entry const& other) const;
|
||||||
|
Entry(ImageOperation&& operation, String&& string);
|
||||||
|
};
|
||||||
|
List<Entry> entries;
|
||||||
|
|
||||||
size_t length() const;
|
size_t length() const;
|
||||||
|
bool operator==(NestedDirectives::Leaf const& other) const;
|
||||||
|
bool operator!=(NestedDirectives::Leaf const& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef function<void(Leaf const&)> LeafCallback;
|
typedef function<void(Leaf const&)> LeafCallback;
|
||||||
@ -51,7 +60,11 @@ public:
|
|||||||
void parseDirectivesIntoLeaf(String const& directives);
|
void parseDirectivesIntoLeaf(String const& directives);
|
||||||
|
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
void append(const NestedDirectives& other);
|
bool compare(NestedDirectives const& other) const;
|
||||||
|
void append(NestedDirectives const& other);
|
||||||
|
NestedDirectives& operator+=(NestedDirectives const& other);
|
||||||
|
bool operator==(NestedDirectives const& other) const;
|
||||||
|
bool operator!=(NestedDirectives const& other) const;
|
||||||
|
|
||||||
const ConstBranch& branch() const;
|
const ConstBranch& branch() const;
|
||||||
|
|
||||||
|
@ -328,12 +328,23 @@ String imageOperationToString(ImageOperation const& operation) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ImageOperation> parseImageOperations(String const& params) {
|
void parseImageOperations(String const& params, function<void(ImageOperation&&)> outputter) {
|
||||||
List<ImageOperation> operations;
|
|
||||||
for (auto const& op : params.split('?')) {
|
for (auto const& op : params.split('?')) {
|
||||||
|
if (!op.empty())
|
||||||
|
outputter(imageOperationFromString(op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ImageOperation> parseImageOperations(String const& params) {
|
||||||
|
auto split = params.split('?');
|
||||||
|
List<ImageOperation> operations;
|
||||||
|
operations.reserve(split.size());
|
||||||
|
|
||||||
|
for (auto const& op : split) {
|
||||||
if (!op.empty())
|
if (!op.empty())
|
||||||
operations.append(imageOperationFromString(op));
|
operations.append(imageOperationFromString(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,216 +363,219 @@ StringList imageOperationReferences(List<ImageOperation> const& operations) {
|
|||||||
return references;
|
return references;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image processImageOperations(List<ImageOperation> const& operations, Image image, ImageReferenceCallback refCallback) {
|
void processImageOperation(ImageOperation const& operation, Image& image, ImageReferenceCallback refCallback) {
|
||||||
for (auto const& operation : operations) {
|
if (auto op = operation.ptr<HueShiftImageOperation>()) {
|
||||||
if (auto op = operation.ptr<HueShiftImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0)
|
||||||
if (pixel[3] != 0)
|
pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount);
|
||||||
pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount);
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<SaturationShiftImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<SaturationShiftImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0) {
|
||||||
if (pixel[3] != 0) {
|
Color color = Color::rgba(pixel);
|
||||||
Color color = Color::rgba(pixel);
|
color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f));
|
||||||
color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f));
|
pixel = color.toRgba();
|
||||||
pixel = color.toRgba();
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<BrightnessMultiplyImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<BrightnessMultiplyImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0) {
|
||||||
if (pixel[3] != 0) {
|
Color color = Color::rgba(pixel);
|
||||||
Color color = Color::rgba(pixel);
|
color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f));
|
||||||
color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f));
|
pixel = color.toRgba();
|
||||||
pixel = color.toRgba();
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<FadeToColorImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<FadeToColorImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
pixel[0] = op->rTable[pixel[0]];
|
||||||
pixel[0] = op->rTable[pixel[0]];
|
pixel[1] = op->gTable[pixel[1]];
|
||||||
pixel[1] = op->gTable[pixel[1]];
|
pixel[2] = op->bTable[pixel[2]];
|
||||||
pixel[2] = op->bTable[pixel[2]];
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<ScanLinesImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<ScanLinesImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) {
|
if (y % 2 == 0) {
|
||||||
if (y % 2 == 0) {
|
pixel[0] = op->fade1.rTable[pixel[0]];
|
||||||
pixel[0] = op->fade1.rTable[pixel[0]];
|
pixel[1] = op->fade1.gTable[pixel[1]];
|
||||||
pixel[1] = op->fade1.gTable[pixel[1]];
|
pixel[2] = op->fade1.bTable[pixel[2]];
|
||||||
pixel[2] = op->fade1.bTable[pixel[2]];
|
} else {
|
||||||
} else {
|
pixel[0] = op->fade2.rTable[pixel[0]];
|
||||||
pixel[0] = op->fade2.rTable[pixel[0]];
|
pixel[1] = op->fade2.gTable[pixel[1]];
|
||||||
pixel[1] = op->fade2.gTable[pixel[1]];
|
pixel[2] = op->fade2.bTable[pixel[2]];
|
||||||
pixel[2] = op->fade2.bTable[pixel[2]];
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<SetColorImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<SetColorImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
pixel[0] = op->color[0];
|
||||||
pixel[0] = op->color[0];
|
pixel[1] = op->color[1];
|
||||||
pixel[1] = op->color[1];
|
pixel[2] = op->color[2];
|
||||||
pixel[2] = op->color[2];
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<ColorReplaceImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<ColorReplaceImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (auto m = op->colorReplaceMap.maybe(pixel))
|
||||||
if (auto m = op->colorReplaceMap.maybe(pixel))
|
pixel = *m;
|
||||||
pixel = *m;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<AlphaMaskImageOperation>()) {
|
} else if (auto op = operation.ptr<AlphaMaskImageOperation>()) {
|
||||||
if (op->maskImages.empty())
|
if (op->maskImages.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!refCallback)
|
if (!refCallback)
|
||||||
throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process");
|
throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process");
|
||||||
|
|
||||||
List<Image const*> maskImages;
|
List<Image const*> maskImages;
|
||||||
for (auto const& reference : op->maskImages)
|
for (auto const& reference : op->maskImages)
|
||||||
maskImages.append(refCallback(reference));
|
maskImages.append(refCallback(reference));
|
||||||
|
|
||||||
image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) {
|
image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) {
|
||||||
uint8_t maskAlpha = 0;
|
uint8_t maskAlpha = 0;
|
||||||
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
||||||
for (auto mask : maskImages) {
|
for (auto mask : maskImages) {
|
||||||
if (pos[0] < mask->width() && pos[1] < mask->height()) {
|
if (pos[0] < mask->width() && pos[1] < mask->height()) {
|
||||||
if (op->mode == AlphaMaskImageOperation::Additive) {
|
if (op->mode == AlphaMaskImageOperation::Additive) {
|
||||||
// We produce our mask alpha from the maximum alpha of any of
|
// We produce our mask alpha from the maximum alpha of any of
|
||||||
// the
|
// the
|
||||||
// mask images.
|
// mask images.
|
||||||
maskAlpha = std::max(maskAlpha, mask->get(pos)[3]);
|
maskAlpha = std::max(maskAlpha, mask->get(pos)[3]);
|
||||||
} else if (op->mode == AlphaMaskImageOperation::Subtractive) {
|
} else if (op->mode == AlphaMaskImageOperation::Subtractive) {
|
||||||
// We produce our mask alpha from the minimum alpha of any of
|
// We produce our mask alpha from the minimum alpha of any of
|
||||||
// the
|
// the
|
||||||
// mask images.
|
// mask images.
|
||||||
maskAlpha = std::min(maskAlpha, mask->get(pos)[3]);
|
maskAlpha = std::min(maskAlpha, mask->get(pos)[3]);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel[3] = std::min(pixel[3], maskAlpha);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<BlendImageOperation>()) {
|
|
||||||
if (op->blendImages.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!refCallback)
|
|
||||||
throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process");
|
|
||||||
|
|
||||||
List<Image const*> blendImages;
|
|
||||||
for (auto const& reference : op->blendImages)
|
|
||||||
blendImages.append(refCallback(reference));
|
|
||||||
|
|
||||||
image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) {
|
|
||||||
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
|
||||||
Vec4F fpixel = Color::v4bToFloat(pixel);
|
|
||||||
for (auto blend : blendImages) {
|
|
||||||
if (pos[0] < blend->width() && pos[1] < blend->height()) {
|
|
||||||
Vec4F blendPixel = Color::v4bToFloat(blend->get(pos));
|
|
||||||
if (op->mode == BlendImageOperation::Multiply)
|
|
||||||
fpixel = fpixel.piecewiseMultiply(blendPixel);
|
|
||||||
else if (op->mode == BlendImageOperation::Screen)
|
|
||||||
fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel = Color::v4fToByte(fpixel);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<MultiplyImageOperation>()) {
|
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
|
||||||
pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t {
|
|
||||||
return (uint8_t)(((int)a * (int)b) / 255);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<BorderImageOperation>()) {
|
|
||||||
Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32);
|
|
||||||
borderImage.copyInto(Vec2U::filled(op->pixels), image);
|
|
||||||
Vec2I borderImageSize = Vec2I(borderImage.size());
|
|
||||||
|
|
||||||
borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) {
|
|
||||||
int pixels = op->pixels;
|
|
||||||
bool includeTransparent = op->includeTransparent;
|
|
||||||
if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) {
|
|
||||||
int dist = std::numeric_limits<int>::max();
|
|
||||||
for (int j = -pixels; j < pixels + 1; j++) {
|
|
||||||
for (int i = -pixels; i < pixels + 1; i++) {
|
|
||||||
if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) {
|
|
||||||
Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels);
|
|
||||||
if (remotePixel[3] != 0) {
|
|
||||||
dist = std::min(dist, abs(i) + abs(j));
|
|
||||||
if (dist == 1) // Early out, if dist is 1 it ain't getting shorter
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dist < std::numeric_limits<int>::max()) {
|
|
||||||
float percent = (dist - 1) / (2.0f * pixels - 1);
|
|
||||||
Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent);
|
|
||||||
if (pixel[3] != 0) {
|
|
||||||
if (op->outlineOnly) {
|
|
||||||
float pixelA = byteToFloat(pixel[3]);
|
|
||||||
color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Color pixelF = Color::rgba(pixel);
|
|
||||||
float pixelA = pixelF.alphaF(), colorA = color.alphaF();
|
|
||||||
colorA += pixelA * (1.0f - colorA);
|
|
||||||
pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate
|
|
||||||
color.convertToLinear();
|
|
||||||
color = color.mix(pixelF, pixelA);
|
|
||||||
color.convertToSRGB();
|
|
||||||
color.setAlphaF(colorA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel = color.toRgba();
|
|
||||||
}
|
|
||||||
} else if (op->outlineOnly) {
|
|
||||||
pixel = Vec4B(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
image = borderImage;
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<ScaleImageOperation>()) {
|
|
||||||
if (op->mode == ScaleImageOperation::Nearest)
|
|
||||||
image = scaleNearest(image, op->scale);
|
|
||||||
else if (op->mode == ScaleImageOperation::Bilinear)
|
|
||||||
image = scaleBilinear(image, op->scale);
|
|
||||||
else if (op->mode == ScaleImageOperation::Bicubic)
|
|
||||||
image = scaleBicubic(image, op->scale);
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<CropImageOperation>()) {
|
|
||||||
image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size()));
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<FlipImageOperation>()) {
|
|
||||||
if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) {
|
|
||||||
for (size_t y = 0; y < image.height(); ++y) {
|
|
||||||
for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) {
|
|
||||||
size_t xRight = image.width() - 1 - xLeft;
|
|
||||||
|
|
||||||
auto left = image.get(xLeft, y);
|
|
||||||
auto right = image.get(xRight, y);
|
|
||||||
|
|
||||||
image.set(xLeft, y, right);
|
|
||||||
image.set(xRight, y, left);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pixel[3] = std::min(pixel[3], maskAlpha);
|
||||||
|
});
|
||||||
|
|
||||||
if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) {
|
} else if (auto op = operation.ptr<BlendImageOperation>()) {
|
||||||
for (size_t x = 0; x < image.width(); ++x) {
|
if (op->blendImages.empty())
|
||||||
for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) {
|
continue;
|
||||||
size_t yBottom = image.height() - 1 - yTop;
|
|
||||||
|
|
||||||
auto top = image.get(x, yTop);
|
if (!refCallback)
|
||||||
auto bottom = image.get(x, yBottom);
|
throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process");
|
||||||
|
|
||||||
image.set(x, yTop, bottom);
|
List<Image const*> blendImages;
|
||||||
image.set(x, yBottom, top);
|
for (auto const& reference : op->blendImages)
|
||||||
|
blendImages.append(refCallback(reference));
|
||||||
|
|
||||||
|
image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) {
|
||||||
|
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
||||||
|
Vec4F fpixel = Color::v4bToFloat(pixel);
|
||||||
|
for (auto blend : blendImages) {
|
||||||
|
if (pos[0] < blend->width() && pos[1] < blend->height()) {
|
||||||
|
Vec4F blendPixel = Color::v4bToFloat(blend->get(pos));
|
||||||
|
if (op->mode == BlendImageOperation::Multiply)
|
||||||
|
fpixel = fpixel.piecewiseMultiply(blendPixel);
|
||||||
|
else if (op->mode == BlendImageOperation::Screen)
|
||||||
|
fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixel = Color::v4fToByte(fpixel);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<MultiplyImageOperation>()) {
|
||||||
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
|
pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t {
|
||||||
|
return (uint8_t)(((int)a * (int)b) / 255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<BorderImageOperation>()) {
|
||||||
|
Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32);
|
||||||
|
borderImage.copyInto(Vec2U::filled(op->pixels), image);
|
||||||
|
Vec2I borderImageSize = Vec2I(borderImage.size());
|
||||||
|
|
||||||
|
borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) {
|
||||||
|
int pixels = op->pixels;
|
||||||
|
bool includeTransparent = op->includeTransparent;
|
||||||
|
if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) {
|
||||||
|
int dist = std::numeric_limits<int>::max();
|
||||||
|
for (int j = -pixels; j < pixels + 1; j++) {
|
||||||
|
for (int i = -pixels; i < pixels + 1; i++) {
|
||||||
|
if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) {
|
||||||
|
Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels);
|
||||||
|
if (remotePixel[3] != 0) {
|
||||||
|
dist = std::min(dist, abs(i) + abs(j));
|
||||||
|
if (dist == 1) // Early out, if dist is 1 it ain't getting shorter
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dist < std::numeric_limits<int>::max()) {
|
||||||
|
float percent = (dist - 1) / (2.0f * pixels - 1);
|
||||||
|
Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent);
|
||||||
|
if (pixel[3] != 0) {
|
||||||
|
if (op->outlineOnly) {
|
||||||
|
float pixelA = byteToFloat(pixel[3]);
|
||||||
|
color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Color pixelF = Color::rgba(pixel);
|
||||||
|
float pixelA = pixelF.alphaF(), colorA = color.alphaF();
|
||||||
|
colorA += pixelA * (1.0f - colorA);
|
||||||
|
pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate
|
||||||
|
color.convertToLinear();
|
||||||
|
color = color.mix(pixelF, pixelA);
|
||||||
|
color.convertToSRGB();
|
||||||
|
color.setAlphaF(colorA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixel = color.toRgba();
|
||||||
|
}
|
||||||
|
} else if (op->outlineOnly) {
|
||||||
|
pixel = Vec4B(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
image = borderImage;
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<ScaleImageOperation>()) {
|
||||||
|
if (op->mode == ScaleImageOperation::Nearest)
|
||||||
|
image = scaleNearest(image, op->scale);
|
||||||
|
else if (op->mode == ScaleImageOperation::Bilinear)
|
||||||
|
image = scaleBilinear(image, op->scale);
|
||||||
|
else if (op->mode == ScaleImageOperation::Bicubic)
|
||||||
|
image = scaleBicubic(image, op->scale);
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<CropImageOperation>()) {
|
||||||
|
image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size()));
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<FlipImageOperation>()) {
|
||||||
|
if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) {
|
||||||
|
for (size_t y = 0; y < image.height(); ++y) {
|
||||||
|
for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) {
|
||||||
|
size_t xRight = image.width() - 1 - xLeft;
|
||||||
|
|
||||||
|
auto left = image.get(xLeft, y);
|
||||||
|
auto right = image.get(xRight, y);
|
||||||
|
|
||||||
|
image.set(xLeft, y, right);
|
||||||
|
image.set(xRight, y, left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) {
|
||||||
|
for (size_t x = 0; x < image.width(); ++x) {
|
||||||
|
for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) {
|
||||||
|
size_t yBottom = image.height() - 1 - yTop;
|
||||||
|
|
||||||
|
auto top = image.get(x, yTop);
|
||||||
|
auto bottom = image.get(x, yBottom);
|
||||||
|
|
||||||
|
image.set(x, yTop, bottom);
|
||||||
|
image.set(x, yBottom, top);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image processImageOperations(List<ImageOperation> const& operations, Image image, ImageReferenceCallback refCallback) {
|
||||||
|
for (auto const& operation : operations)
|
||||||
|
processImageOperation(operation, image, refCallback);
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,8 @@ typedef Variant<HueShiftImageOperation, SaturationShiftImageOperation, Brightnes
|
|||||||
ImageOperation imageOperationFromString(String const& string);
|
ImageOperation imageOperationFromString(String const& string);
|
||||||
String imageOperationToString(ImageOperation const& operation);
|
String imageOperationToString(ImageOperation const& operation);
|
||||||
|
|
||||||
|
void parseImageOperations(String const& params, function<void(ImageOperation&&)> outputter);
|
||||||
|
|
||||||
// Each operation is assumed to be separated by '?', with parameters
|
// Each operation is assumed to be separated by '?', with parameters
|
||||||
// separated by ';' or '='
|
// separated by ';' or '='
|
||||||
List<ImageOperation> parseImageOperations(String const& params);
|
List<ImageOperation> parseImageOperations(String const& params);
|
||||||
@ -147,6 +149,8 @@ StringList imageOperationReferences(List<ImageOperation> const& operations);
|
|||||||
|
|
||||||
typedef function<Image const*(String const& refName)> ImageReferenceCallback;
|
typedef function<Image const*(String const& refName)> ImageReferenceCallback;
|
||||||
|
|
||||||
|
void processImageOperation(ImageOperation const& operation, Image& input, ImageReferenceCallback refCallback = {});
|
||||||
|
|
||||||
Image processImageOperations(List<ImageOperation> const& operations, Image input, ImageReferenceCallback refCallback = {});
|
Image processImageOperations(List<ImageOperation> const& operations, Image input, ImageReferenceCallback refCallback = {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user