initial import

This commit is contained in:
Sascha Nitsch 2022-01-17 15:53:17 +01:00
commit 8c21e7ac6a
16 changed files with 3206 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
build
.cproject
.project
.settings/
c++/html

2
CPPLINT.cfg Normal file
View file

@ -0,0 +1,2 @@
filter=-build/include_subdir,-whitespace/line_length
root=cpp

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 GrumyDeveloper https://contentnation.net/en/grumpydevelop/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Path animator
Source code for the Path animator framework on [the project page](https://contentnation.net/en/grumpydevelop/pathanimator).
Initial implementation in C++ running on Linux, testing needs to be done if it can be in-browser via Javascript to make it cross plattform client only.

41
c++/CMakeLists.txt Normal file
View file

@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.0.0)
set(CMAKE_BUILD_TYPE None)
set(config release CACHE STRING "build mode <debug|native|generic>")
cmake_policy(SET CMP0017 NEW)
project(pathanimator)
enable_testing()
if (config STREQUAL "debug")
message("building debug version")
add_definitions(-Wall -Wextra -O0 -g)
set(CMAKE_EXE_LINKER_FLAGS "-O0 -g")
set(CMAKE_SHARED_LINKER_FLAGS "-O0 -g")
set(CMAKE_MODULE_LINKER_FLAGS "-O0 -g")
elseif (config STREQUAL "native")
message("building release version optimized for local CPU")
add_definitions(-Wall -Wextra -O3 -march=native -mtune=native)
set(CMAKE_EXE_LINKER_FLAGS "-O3 -march=native -mtune=native")
set(CMAKE_SHARED_LINKER_FLAGS "-O3 -march=native -mtune=native")
set(CMAKE_MODULE_LINKER_FLAGS "-O3 -march=native -mtune=native")
message("building generic release version")
add_definitions(-Wall -Wextra -O3)
set(CMAKE_EXE_LINKER_FLAGS "-O3 ")
set(CMAKE_SHARED_LINKER_FLAGS "-O3 ")
set(CMAKE_MODULE_LINKER_FLAGS "-O3 ")
endif()
add_definitions(-DPROJECT_ROOT="${PROJECT_SOURCE_DIR}")
add_definitions(-std=c++11)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_C_STANDARD 11)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
find_package(OpenImageIO REQUIRED)
include_directories (
${CMAKE_SOURCE_DIR}/
)
add_subdirectory(algorithm)
add_executable(pathanimator init.cpp main.cpp image.cpp)
target_link_libraries(pathanimator algorithms OpenImageIO)

2642
c++/Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
add_library(algorithms distancepath.cpp)

22
c++/algorithm/README.md Normal file
View file

@ -0,0 +1,22 @@
# Algrithm descriptions
## Distance Path
This algorithm uses a simple distance calculation based from one or more start points.
1. Initialization
* The start points are added to the "to process" queue.
* The distance values on all pixels are set to m_maxDistance on start.
2. Main loop
* Each point in the "to process" queue is taken and it's value (distance).
* Each surrounding pixel takes the last distance (the center) and adds the distance given by the parameters m_weighBlock and m_weightPath.
The input image selects the weight from the two parameters, a 0 (black) pixel takes 100% of the block value, a (255) white pixel
is 100% path weight.
* If this distance is smaller than the previous pixel that was calulated, replace it and add the pixel to the next list of pixel to be processed.
* After the iteration is done, clear the process queue and swap with the next queue and repeat until the next queue is empty or the hard limit of 10.000 iterations is met.
3. Postprocessing
* After the raw distances are calculated, the actual max distance is found.
* The raw map (floating ponts) is mapped down to a 16 bit image and the distance is saved in the red channel,
* the iteration where the pixel was set is in the green channel
* the blue channel is a mask if the pixel was seen as a path (65535) or as a block (0).
The m_threshhold value sets the point where a path becomes a block.
Setting the threshhold allows some cheating. You can add an path between areas with a gray value, the algorithm uses it to calculate
distance and as a path to other ares, but shows it as a block in the blue channel.

28
c++/algorithm/algorithm.h Normal file
View file

@ -0,0 +1,28 @@
/// \file algorithm/algorithm.h
/// \copyright 2022 Sascha Nitsch
/// Licence under MIT license
/// \brief declaration of the Algorithm class
#ifndef ALGORITHM_ALGORITHM_H_
#define ALGORITHM_ALGORITHM_H_
// system includes
#include <inttypes.h>
// own includes
#include "../image.h"
/// namespace for different time generation algorithms
namespace Algorithm {
/// \brief base class for the different time generation algorithms
class Algorithm {
public:
/// \brief destructor
virtual ~Algorithm() {}
/// \brief process images
/// \param input input image
/// \param output output image
/// \return 0 if ok, != 0 on error
virtual int8_t process(Image* input, Image* output) = 0;
};
} // namespace Algorithm
#endif // ALGORITHM_ALGORITHM_H_

View file

@ -0,0 +1,133 @@
/// \file algorithm/distancepath.cpp
/// \copyright 2022 Sascha Nitsch
/// Licenced under MIT license
/// \brief implementation of the DistancePath class
// system includes
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <utility>
// own includes
#include "distancepath.h"
namespace Algorithm {
DistancePath::DistancePath(int argc, const char* argv[]) {
if (argc < 6) {
printf("DistancePath needs min 6 arguments <weight for traversalable pixel> <weight for blocked pixel> <Threshhold to mark as traversable> <max Distance> <StartX> <StartY> [<StartX> <StartY>] ...");
} else {
m_weightPath = atof(argv[0]);
m_weightBlock = atof(argv[1]);
m_threshhold = atoi(argv[2]);
m_maxDistance = atof(argv[3]);
for (uint8_t i = 4; i < argc; i+=2) {
m_queue1.push_back((uint32_t)(atoi(argv[i]) << 16) + atoi(argv[i + 1]));
}
}
}
DistancePath::~DistancePath() {
}
inline void DistancePath::processPixel(uint8_t* inRAW, float* outRow, uint16_t x, uint16_t y, float lastValue, uint16_t width, std::list<uint32_t>* next, uint16_t* outPixels, uint16_t iteration) {
float *oR = &outRow[x];
uint32_t offset = (y * width + x) * 3;
float avg = 255 - (0.3333 * (inRAW[offset] + inRAW[offset + 1] + inRAW[offset + 2]));
float newValue = lastValue + avg * m_weightBlock + (255.0 - avg) * m_weightPath;
if (newValue > m_maxDistance) newValue = m_maxDistance;
if (newValue < *oR) {
next->push_back((x << 16) + y);
outPixels[offset + 1] = iteration;
*oR = newValue;
}
}
inline void DistancePath::processRow(uint8_t* inRAW, float* outRAW, uint16_t x, uint16_t y, float lastValue, uint16_t width, std::list<uint32_t>* next, uint16_t* outPixels, uint16_t iteration) {
float* outRow = &outRAW[y * width];
if (x > 0) {
processPixel(inRAW, outRow, x - 1, y, lastValue, width, next, outPixels, iteration);
}
processPixel(inRAW, outRow, x, y, lastValue, width, next, outPixels, iteration);
if (x < width - 1) {
processPixel(inRAW, outRow, x + 1, y, lastValue, width, next, outPixels, iteration);
}
}
int8_t DistancePath::process(Image* input, Image* output) {
std::list<uint32_t> queue2;
std::list<uint32_t>* last = &m_queue1;
std::list<uint32_t>* next = &queue2;
uint16_t width = input->getWidth();
uint16_t height = input->getHeight();
uint8_t* inRAW = input->getPixels8();
uint32_t end = width * height;
// allocate output storage and set to m_maxDistance
float* outRAW = reinterpret_cast<float*>(malloc(width * height * sizeof(float)));
for (uint32_t i = 0; i < end; ++i) {
outRAW[i] = m_maxDistance;
}
// set start pixels to distance of 0
uint16_t* outPixels = output->getPixels16();
for (uint32_t coordinate : m_queue1) {
uint16_t x = coordinate >> 16;
uint16_t y = coordinate & 0xFFFFFFFF;
outRAW[(width * y + x)] = 0;
}
uint16_t iteration = 1;
do {
// process pixel from last time
for (uint32_t coordinate : *last) {
uint16_t x = coordinate >> 16;
uint16_t y = coordinate & 0xFFFFFFFF;
float lastValue = outRAW[y * width + x];
if (y > 0) { // process row on top
processRow(inRAW, outRAW, x, y-1, lastValue, width, next, outPixels, iteration);
}
processRow(inRAW, outRAW, x, y, lastValue, width, next, outPixels, iteration);
if (y < height - 1) { // process row on bottom
processRow(inRAW, outRAW, x, y+1, lastValue, width, next, outPixels, iteration);
}
}
++iteration;
// clear out old list
last->clear();
// swap list to get new input queue
std::swap(last, next);
if (iteration > 10000) break;
} while (next->size() > 0);
printf("iterations: %i\n", iteration);
// find max value
float max = 0;
uint16_t threshhold = m_threshhold * 3; // multiply by 3 here saves many division from sum to average in the loops below
// could be optimized, but it is only run once for every pixel
for (uint32_t i = 0; i < end; ++i) {
uint32_t offset = i * 3;
uint16_t sum = (inRAW[offset] + inRAW[offset + 1] + inRAW[offset + 2]);
// only update max if it is larger than previous, but still smaller than our max distance and larger as the threshhold (visible path cheat)
if (outRAW[i] > max && outRAW[i] < m_maxDistance && (sum > threshhold)) max = outRAW[i];
}
float iterMult = 65535.0 / iteration; // multiplicator to map iteration from 0 to 65535
max = 65535.0 / max; // results in 0 ... 65535 range
// write result to output
uint32_t offset = 0;
for (uint32_t i = 0; i < end; ++i) {
uint16_t sum = (inRAW[offset] + inRAW[offset + 1] + inRAW[offset + 2]);
if (sum > threshhold) {
outPixels[offset] = outRAW[i] * max; // red = distance
outPixels[offset + 1] = iterMult * outPixels[offset + 1]; // green = iteration
outPixels[offset + 2] = 65535; // blue= a pathable pixel
} else {
outPixels[offset] = 0;
outPixels[offset + 1] = 0;
outPixels[offset + 2] = 0;
}
offset += 3;
}
free(outRAW);
return 0;
}
} // namespace Algorithm

View file

@ -0,0 +1,75 @@
/// \file algorithm/distancepath.h
/// \copyright 2022 Sascha Nitsch
/// Licenced under MIT license
/// \brief declaration of the DistancePath class
#ifndef ALGORITHM_DISTANCEPATH_H_
#define ALGORITHM_DISTANCEPATH_H_
// system includes
#include <list>
// own includes
#include "algorithm.h"
// forward declaration
class Image;
namespace Algorithm {
/// \brief calculate value based on distance to start point
/// for a long description, see README.md
class DistancePath : public Algorithm {
public:
/// \brief constructor
/// \param argc number of arguments
/// \param argv arguments
DistancePath(int argc, const char* argv[]);
/// \brief destructor
~DistancePath();
/// \brief process images
/// \param input input image
/// \param output output image
/// \return 0 if ok, != 0 on error
int8_t process(Image* input, Image* output);
private:
/// \brief do actual calculation on pixel
/// \param inRAW pointer to raw image input
/// \param outRow pointer to current row in output
/// \param x x coordinate
/// \param y y coordinate
/// \param lastValue last value from source pixel
/// \param width image width
/// \param next list to put new pixel to
/// \param outPixels pointer to output pixel array
/// \param iteration current iteration number
inline void processPixel(uint8_t* inRAW, float* outRow, uint16_t x, uint16_t y, float lastValue, uint16_t width, std::list<uint32_t>* next, uint16_t* outPixels, uint16_t iteration);
/// \brief do actual calculation on pixel row
/// \param inRAW pointer to raw image input
/// \param outRAW pointer to output
/// \param x x coordinate
/// \param y y coordinate
/// \param lastValue last value from source pixel
/// \param width image width
/// \param next list to put new pixel to
/// \param outPixels pointer to output pixel array
/// \param iteration current iteration number
inline void processRow(uint8_t* inRAW, float* outRAW, uint16_t x, uint16_t y, float lastValue, uint16_t width, std::list<uint32_t>* next, uint16_t* outPixels, uint16_t iteration);
/// weight for pathable neighbour
float m_weightPath;
/// weight for blocked neighbour
float m_weightBlock;
/// top cap
float m_maxDistance;
/// threshhold to mark pixel as traversable in output
uint8_t m_threshhold;
/// queue of pixels to process
std::list<uint32_t> m_queue1;
};
} // namespace Algorithm
#endif // ALGORITHM_DISTANCEPATH_H_

53
c++/image.cpp Normal file
View file

@ -0,0 +1,53 @@
/// \file image.cpp
/// \copyright 2022 Sascha Nitsch
/// Licenced under MIT
/// \brief definition of the main class of the application
// system includes
#include <stdlib.h>
#include <string.h>
// own includes
#include "image.h"
Image::Image(uint16_t width, uint16_t height, uint8_t channels, uint8_t bits, void* pixels) {
m_width = width;
m_height = height;
m_channels = channels;
m_pixels = pixels;
m_ownPixel = false;
m_bitsPerChannel = bits;
}
Image::~Image() {
if (m_ownPixel && m_pixels) {
free(m_pixels);
}
}
bool Image::allocateMemory() {
if (m_ownPixel && m_pixels) {
free(m_pixels);
}
m_pixels = reinterpret_cast<uint8_t*>(malloc(m_width * m_height * m_channels * m_bitsPerChannel / 8));
bzero(m_pixels, m_width * m_height * m_channels * m_bitsPerChannel / 8);
if (m_pixels) {
m_ownPixel = true;
return true;
}
return false;
}
uint8_t* Image::getPixels8() const {
return reinterpret_cast<uint8_t*>(m_pixels);
}
uint16_t* Image::getPixels16() const {
return reinterpret_cast<uint16_t*>(m_pixels);
}
uint16_t Image::getHeight() const {
return m_height;
}
uint16_t Image::getWidth() const {
return m_width;
}

52
c++/image.h Normal file
View file

@ -0,0 +1,52 @@
/// \file image.h
/// \copyright 2022 Sascha Nitsch
/// Licence under MIT license
/// \brief declaration of the Image class
#ifndef IMAGE_H_
#define IMAGE_H_
// system includes
#include <inttypes.h>
/// wrapper for pixel data of an image
class Image {
public:
/// constructor
/// \param width width in pixel
/// \param height height in pixel
/// \param channels number of channels
/// \param bits number of bits per channel
/// \param pixels optional memory for the image
Image(uint16_t width, uint16_t height, uint8_t channels, uint8_t bits, void* pixels = nullptr);
/// destructor
~Image();
/// \brief allocate memory for image data
bool allocateMemory();
/// \brief get pointer to pixel data
/// \return pointer to pixel data
uint8_t* getPixels8() const;
/// \brief get pointer to pixel data
/// \return pointer to pixel data
uint16_t* getPixels16() const;
/// \brief get width of image
/// \return width of image
uint16_t getWidth() const;
/// \brief get height of image
/// \return height of image
uint16_t getHeight() const;
private:
/// image width
uint16_t m_width;
/// image height
uint16_t m_height;
/// number of channels
uint8_t m_channels;
/// image data as RGB 8 or 16 bit per channel
void* m_pixels;
/// do we own the pixel memory?
bool m_ownPixel;
/// number of bits per channel
uint8_t m_bitsPerChannel;
};
#endif // IMAGE_H_

18
c++/init.cpp Normal file
View file

@ -0,0 +1,18 @@
/// \file init.cpp
/// \copyright 2022 Sascha Nitsch
/// Licencend under MIT license
/// \biref main entry point of the application
// own includes
#include "main.h"
/// \brief main entry point
/// \param argc number of argument
/// \param argv array of arguments
int main(int argc, const char* argv[]) {
Main main;
if (main.init(argc, argv) && main.run()) {
return 0;
}
return 1;
}

79
c++/main.cpp Normal file
View file

@ -0,0 +1,79 @@
/// \file main.cpp
/// \copyright 2022 Sascha Nitsch
/// Licenced under MIT
/// \brief definition of the main class of the application
// system includes
#include <stdio.h>
#include <string.h>
#include <OpenImageIO/imageio.h>
// own includes
#include "main.h"
// algorithms
#include "algorithm/distancepath.h"
Main::Main() {
m_algorithm = NULL;
}
Main::~Main() {
delete m_algorithm;
}
bool Main::init(int argc, const char* argv[]) {
if (argc < 3) {
printUsage(argv[0]);
return false;
}
m_inputFilename = argv[1];
m_outputFilename = argv[2];
const char* algorithmName = argv[3];
if (!strcmp(algorithmName, "distancepath")) {
m_algorithm = new Algorithm::DistancePath(argc - 4, &argv[4]);
}
if (m_algorithm) {
return true;
}
printf("algorithm \"%s\" not found\n", algorithmName);
return false;
}
void Main::printUsage(const char* programName) {
printf("Usage: %s input.png output.png algorithm <algoritm arguments>\n", programName);
}
bool Main::run() {
if (!m_algorithm) {
return false;
}
// load input image
auto inp = OIIO::ImageInput::open(m_inputFilename);
if (!inp) {
printf("Loading of input image \"%s\" failed %s\n", m_inputFilename.c_str(), OIIO::geterror().c_str());
return false;
}
const OIIO::ImageSpec &spec = inp->spec();
Image input(spec.width, spec.height, spec.nchannels, 8);
input.allocateMemory();
inp->read_image(0, 0, 0, 3, OIIO::TypeDesc::UINT8, input.getPixels8());
inp->close();
// create output image
Image output(spec.width, spec.height, 3, 16);
output.allocateMemory();
int ret = m_algorithm->process(&input, &output);
if (ret == 0) {
OIIO::ImageSpec ospec;
ospec.width = spec.width;
ospec.height = spec.height;
ospec.nchannels = 3;
ospec.format = OIIO::TypeDesc::UINT16;
auto outp = OIIO::ImageOutput::create(m_outputFilename);
if (outp) {
outp->open(m_outputFilename, ospec);
outp->write_image(OIIO::TypeDesc::UINT16, output.getPixels16());
outp->close();
}
}
return ret == 0;
}

42
c++/main.h Normal file
View file

@ -0,0 +1,42 @@
/// \file main.h
/// \copyright 2022 Sascha Nitsch
/// Licence under MIT license
/// \brief declaration of the main class for the application
#ifndef MAIN_H_
#define MAIN_H_
// system includes
#include <inttypes.h>
#include <string>
// own includes
#include "algorithm/algorithm.h"
/// main class
class Main {
public:
/// \brief constructor
Main();
/// \brief destructor
~Main();
/// \brief initialize algorithm
/// \param argc number of arguments
/// \param argv list of arguments
/// \return true on success
bool init(int argc, const char* argv[]);
/// \brief run algorithm
/// \return true on success
bool run();
private:
/// \brief print program usage
/// \param programName name of our program
void printUsage(const char* programName);
/// input image filename
std::string m_inputFilename;
/// output image filename
std::string m_outputFilename;
/// pointer to the to be used algorithm
Algorithm::Algorithm* m_algorithm;
};
#endif // MAIN_H_