diff --git a/source/core/StarAssetPath.cpp b/source/core/StarAssetPath.cpp index 3a6a83e..2100ee0 100644 --- a/source/core/StarAssetPath.cpp +++ b/source/core/StarAssetPath.cpp @@ -147,6 +147,10 @@ bool AssetPath::operator==(AssetPath const& rhs) const { 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) { os << rhs.basePath; if (rhs.subPath) { diff --git a/source/core/StarAssetPath.hpp b/source/core/StarAssetPath.hpp index e51acee..9da30e7 100644 --- a/source/core/StarAssetPath.hpp +++ b/source/core/StarAssetPath.hpp @@ -51,6 +51,9 @@ struct AssetPath { // sourcePath. static String relativeTo(String const& sourcePath, String const& givenPath); + AssetPath() = default; + AssetPath(String const& path); + String basePath; Maybe subPath; NestedDirectives directives; diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp index 62a2a49..2394915 100644 --- a/source/core/StarDirectives.cpp +++ b/source/core/StarDirectives.cpp @@ -1,6 +1,8 @@ #include "StarImage.hpp" #include "StarImageProcessing.hpp" #include "StarDirectives.hpp" +#include "StarXXHash.hpp" +#include "StarHash.hpp" namespace Star { @@ -16,22 +18,32 @@ NestedDirectives::NestedDirectives(String&& directives) { void NestedDirectives::parseDirectivesIntoLeaf(String const& directives) { Leaf leaf; for (String& op : directives.split('?')) { - if (!op.empty()) { - leaf.operations.append(imageOperationFromString(op)); - leaf.strings.append(move(op)); - } + if (!op.empty()) + leaf.entries.emplace_back(imageOperationFromString(op), op); } m_root = std::make_shared(move(leaf)); } -bool NestedDirectives::empty() const { +inline bool NestedDirectives::empty() const { 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()); } +NestedDirectives& NestedDirectives::operator+=(NestedDirectives const& other) { + append(other); + return *this; +} + String NestedDirectives::toString() const { String string; addToString(string); @@ -51,9 +63,8 @@ void NestedDirectives::forEach(LeafCallback callback) const { void NestedDirectives::forEachPair(LeafPairCallback callback) const { if (m_root) { LeafCallback pairCallback = [&](Leaf const& leaf) { - size_t length = leaf.length(); - for (size_t i = 0; i != length; ++i) - callback(leaf.operations.at(i), leaf.strings.at(i)); + for (auto& entry : leaf.entries) + callback(entry.operation, entry.string); }; m_root->forEach(pairCallback); } @@ -71,9 +82,8 @@ bool NestedDirectives::forEachPairAbortable(AbortableLeafPairCallback callback) return false; else { AbortableLeafCallback pairCallback = [&](Leaf const& leaf) -> bool { - size_t length = leaf.length(); - for (size_t i = 0; i != length; ++i) { - if (!callback(leaf.operations.at(i), leaf.strings.at(i))) + for (auto& entry : leaf.entries) { + if (!callback(entry.operation, entry.string)) return false; } @@ -85,8 +95,8 @@ bool NestedDirectives::forEachPairAbortable(AbortableLeafPairCallback callback) Image NestedDirectives::apply(Image& image) const { Image current = image; - forEach([&](Leaf const& leaf) { - current = processImageOperations(leaf.operations, current); + forEachPair([&](ImageOperation const& operation, String const& string) { + processImageOperation(operation, current); }); return current; } @@ -105,11 +115,33 @@ NestedDirectives::Branches& NestedDirectives::convertToBranches() { return m_root->value.get(); } -size_t NestedDirectives::Leaf::length() const { - if (operations.size() != strings.size()) - throw DirectivesException("NestedDirectives leaf has mismatching operation/string List sizes"); +bool NestedDirectives::Leaf::Entry::operator==(NestedDirectives::Leaf::Entry const& other) const { + return string == other.string; +} - 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()) {}; @@ -118,11 +150,33 @@ NestedDirectives::Cell::Cell(Branches&& branches) : value(move(branches)) {}; NestedDirectives::Cell::Cell(const Leaf& leaf) : value(leaf) {}; NestedDirectives::Cell::Cell(const Branches& branches) : value(branches) {}; +/* +bool NestedDirectives::Cell::operator==(NestedDirectives::Cell const& other) const { + if (auto leaf = value.ptr()) { + if (auto otherLeaf = other.value.ptr()) + return *leaf == *otherLeaf; + else { + + } + } + else { + for (auto& branch : value.get()) { + + } + } +} + + +bool NestedDirectives::Cell::operator!=(NestedDirectives::Cell const& other) const { + return !(*this == other); +} +//*/ + void NestedDirectives::Cell::buildString(String& string) const { if (auto leaf = value.ptr()) - for (auto& leafString : leaf->strings) { + for (auto& entry : leaf->entries) { string += "?"; - string += leafString; + string += entry.string; } else { for (auto& branch : value.get()) diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index bc5b78d..924f295 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -12,10 +12,19 @@ STAR_EXCEPTION(DirectivesException, StarException); class NestedDirectives { public: struct Leaf { - List operations; - List strings; + struct Entry { + ImageOperation operation; + String string; + + bool operator==(Entry const& other) const; + bool operator!=(Entry const& other) const; + Entry(ImageOperation&& operation, String&& string); + }; + List entries; size_t length() const; + bool operator==(NestedDirectives::Leaf const& other) const; + bool operator!=(NestedDirectives::Leaf const& other) const; }; typedef function LeafCallback; @@ -51,7 +60,11 @@ public: void parseDirectivesIntoLeaf(String const& directives); 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; diff --git a/source/core/StarImageProcessing.cpp b/source/core/StarImageProcessing.cpp index fd07e1c..fc99f69 100644 --- a/source/core/StarImageProcessing.cpp +++ b/source/core/StarImageProcessing.cpp @@ -328,12 +328,23 @@ String imageOperationToString(ImageOperation const& operation) { return ""; } -List parseImageOperations(String const& params) { - List operations; +void parseImageOperations(String const& params, function outputter) { for (auto const& op : params.split('?')) { + if (!op.empty()) + outputter(imageOperationFromString(op)); + } +} + +List parseImageOperations(String const& params) { + auto split = params.split('?'); + List operations; + operations.reserve(split.size()); + + for (auto const& op : split) { if (!op.empty()) operations.append(imageOperationFromString(op)); } + return operations; } @@ -352,216 +363,219 @@ StringList imageOperationReferences(List const& operations) { return references; } -Image processImageOperations(List const& operations, Image image, ImageReferenceCallback refCallback) { - for (auto const& operation : operations) { - if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) - pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount); - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) { - Color color = Color::rgba(pixel); - color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f)); - pixel = color.toRgba(); - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) { - Color color = Color::rgba(pixel); - color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f)); - pixel = color.toRgba(); - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - pixel[0] = op->rTable[pixel[0]]; - pixel[1] = op->gTable[pixel[1]]; - pixel[2] = op->bTable[pixel[2]]; - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) { - if (y % 2 == 0) { - pixel[0] = op->fade1.rTable[pixel[0]]; - pixel[1] = op->fade1.gTable[pixel[1]]; - pixel[2] = op->fade1.bTable[pixel[2]]; - } else { - pixel[0] = op->fade2.rTable[pixel[0]]; - pixel[1] = op->fade2.gTable[pixel[1]]; - pixel[2] = op->fade2.bTable[pixel[2]]; - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - pixel[0] = op->color[0]; - pixel[1] = op->color[1]; - pixel[2] = op->color[2]; - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (auto m = op->colorReplaceMap.maybe(pixel)) - pixel = *m; - }); +void processImageOperation(ImageOperation const& operation, Image& image, ImageReferenceCallback refCallback) { + if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) + pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount); + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) { + Color color = Color::rgba(pixel); + color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f)); + pixel = color.toRgba(); + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) { + Color color = Color::rgba(pixel); + color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f)); + pixel = color.toRgba(); + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + pixel[0] = op->rTable[pixel[0]]; + pixel[1] = op->gTable[pixel[1]]; + pixel[2] = op->bTable[pixel[2]]; + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) { + if (y % 2 == 0) { + pixel[0] = op->fade1.rTable[pixel[0]]; + pixel[1] = op->fade1.gTable[pixel[1]]; + pixel[2] = op->fade1.bTable[pixel[2]]; + } else { + pixel[0] = op->fade2.rTable[pixel[0]]; + pixel[1] = op->fade2.gTable[pixel[1]]; + pixel[2] = op->fade2.bTable[pixel[2]]; + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + pixel[0] = op->color[0]; + pixel[1] = op->color[1]; + pixel[2] = op->color[2]; + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (auto m = op->colorReplaceMap.maybe(pixel)) + pixel = *m; + }); - } else if (auto op = operation.ptr()) { - if (op->maskImages.empty()) - continue; + } else if (auto op = operation.ptr()) { + if (op->maskImages.empty()) + continue; - if (!refCallback) - throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process"); + if (!refCallback) + throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process"); - List maskImages; - for (auto const& reference : op->maskImages) - maskImages.append(refCallback(reference)); + List maskImages; + for (auto const& reference : op->maskImages) + maskImages.append(refCallback(reference)); - image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) { - uint8_t maskAlpha = 0; - Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); - for (auto mask : maskImages) { - if (pos[0] < mask->width() && pos[1] < mask->height()) { - if (op->mode == AlphaMaskImageOperation::Additive) { - // We produce our mask alpha from the maximum alpha of any of - // the - // mask images. - maskAlpha = std::max(maskAlpha, mask->get(pos)[3]); - } else if (op->mode == AlphaMaskImageOperation::Subtractive) { - // We produce our mask alpha from the minimum alpha of any of - // the - // mask images. - maskAlpha = std::min(maskAlpha, mask->get(pos)[3]); - } - } - } - pixel[3] = std::min(pixel[3], maskAlpha); - }); - - } else if (auto op = operation.ptr()) { - if (op->blendImages.empty()) - continue; - - if (!refCallback) - throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process"); - - List 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()) { - 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()) { - 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::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::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()) { - 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()) { - image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size())); - - } else if (auto op = operation.ptr()) { - 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); + image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) { + uint8_t maskAlpha = 0; + Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); + for (auto mask : maskImages) { + if (pos[0] < mask->width() && pos[1] < mask->height()) { + if (op->mode == AlphaMaskImageOperation::Additive) { + // We produce our mask alpha from the maximum alpha of any of + // the + // mask images. + maskAlpha = std::max(maskAlpha, mask->get(pos)[3]); + } else if (op->mode == AlphaMaskImageOperation::Subtractive) { + // We produce our mask alpha from the minimum alpha of any of + // the + // mask images. + maskAlpha = std::min(maskAlpha, mask->get(pos)[3]); } } } + pixel[3] = std::min(pixel[3], maskAlpha); + }); - 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; + } else if (auto op = operation.ptr()) { + if (op->blendImages.empty()) + continue; - auto top = image.get(x, yTop); - auto bottom = image.get(x, yBottom); + if (!refCallback) + throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process"); - image.set(x, yTop, bottom); - image.set(x, yBottom, top); + List 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()) { + 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()) { + 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::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::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()) { + 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()) { + image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size())); + + } else if (auto op = operation.ptr()) { + 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 const& operations, Image image, ImageReferenceCallback refCallback) { + for (auto const& operation : operations) + processImageOperation(operation, image, refCallback); return image; } diff --git a/source/core/StarImageProcessing.hpp b/source/core/StarImageProcessing.hpp index bcb5936..ac6151e 100644 --- a/source/core/StarImageProcessing.hpp +++ b/source/core/StarImageProcessing.hpp @@ -136,6 +136,8 @@ typedef Variant outputter); + // Each operation is assumed to be separated by '?', with parameters // separated by ';' or '=' List parseImageOperations(String const& params); @@ -147,6 +149,8 @@ StringList imageOperationReferences(List const& operations); typedef function ImageReferenceCallback; +void processImageOperation(ImageOperation const& operation, Image& input, ImageReferenceCallback refCallback = {}); + Image processImageOperations(List const& operations, Image input, ImageReferenceCallback refCallback = {}); }