Skip to content

Commit

Permalink
Merge pull request #33 from starturtle/13-refactor-logging
Browse files Browse the repository at this point in the history
  • Loading branch information
starturtle authored Oct 30, 2020
2 parents 9a4714d + c4e70a4 commit dfbd637
Show file tree
Hide file tree
Showing 18 changed files with 580 additions and 571 deletions.
8 changes: 6 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ endif()
# executable setup
add_executable( one_bit main.cpp script_mode.h script_mode.cpp ${RESOURCES})

target_include_directories( one_bit PUBLIC ${OpenCV_INCLUDE_DIRS} ${DOCTEST_INCLUDE_DIR})
target_include_directories( one_bit PUBLIC ${OpenCV_INCLUDE_DIRS})
target_link_libraries( one_bit PUBLIC imaging utilities ${OpenCV_LIBS} )
if( ${USE_QT5} )
message(STATUS "build with GUI mode")
target_include_directories( one_bit PUBLIC ${Qt5_DIR} )
target_link_libraries( one_bit PUBLIC qtgui Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
endif()

enable_testing()
if(DOCTEST_INCLUDE_DIR)
message(STATUS "build test projects")
enable_testing()
endif()

40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,50 @@ This tool is aimed at people crafting with thread, in the more literal sense of
I've often stumbled upon the problem that I wanted to knit a pattern that had not yet been converted into an actual two-color stitch pattern, or that the gauge for my material wouldn't fit the pattern's aspect ratio.
To solve the problem, I started painting over digital images, hoping the gauge would hold. I succeeded eventually, but one pattern could take up to five hours.

## Installation

### Requirements
Disclaimer: I have not made any experiments wrt backwards compatibility yet.
#### OpenCV
This project requires [OpenCV](https://opencv.org/releases/) to be built. I'm working with version 4.2.
The opencv root folder must be part of your CMAKE_MODULE_PATH.
#### CMake
Configuration of this project requires [CMake](https://cmake.org/download/) to be installed. I'm using 3.15.2. You need CTest if you wish to build the unit tests.
#### Qt5
Building the program such that it has a GUI mode requires a [Qt SDK](https://www.qt.io/download) to be installed. I'm using 5.14.2.
The libqt5 root folder must be part of your CMAKE_MODULE_PATH. You need at least the packages Core, Gui, Network, QmlModes, Qml, QuickCompiler and Quick.
#### Doctest
Testing requires [doctest](https://github.com/onqtam/doctest) on your machine. Only for running the program, it is not necessary.
Doctest is header only, so it doesn't technically get installed. To use it with this program, provide the config entry DOCTEST_INCLUDE_DIR as the path to your doctest copy (root directory) when configuring the project in CMake.

## Automatic generation

### Input Data
The input image must be of JPG or PNG format. Note that for proper pixelation, you need to know the gauge and colors of your yarn and the desired size of the workpiece.

### Recomputation
### Shell Mode
From the gauge and result size, the amount and dimensions of one "stixel" (stitch-sized pixel) can directly be computed.
The algorithm will then determine the overall color of that stixel in original colors, then figure out which of the yarn colors comes closest.
In shell mode, you can only specify where to crop the image if its aspect ratio doesn't match your output. Your choice is one of:
* TOP_LEFT
* TOP
* TOP_RIGHT
* LEFT
* CENTER
* RIGHT
* BOTTOM_LEFT
* BOTTOM
* BOTTOM_RIGHT

The output image will be a cropped version of the original one, set as stixels of the aspect ratio computed earlier and of the color determined in the previous step.

Note that for successful computation, you must specify an output path. The input file is not overwritten unless you explicitly define its path as the output file.
Note that for successful computation, you must specify an output path. The input file is not overwritten unless you explicitly define its path as the output file.

### GUI Mode
Select the input file from the File menu using "Load...". If successful, the image will show in the input window, and a first preview will be calculated.
Use the text input fields to determine gauge size and desired output size of your workpiece. The preview will adapt.
You can select a ROI from the input image. The aspect ratio is fixed to the one you specified as desired result size. You are not allowed to exceed input image range.
By default, you have two result stitch colors in the list. Using the Change button, you can select different output colors that match your yarn. The Add button allows you to add up to four colors. The Remove button allows you to reduce it back to at least two.
The stitches are separated by helper lines (complete with a highlight color to help you count). If the colors don't work well with your yarn colors, you can adapt them in the same way. You can also disable helper grids using the check box at the top.
The preview will adjust each time you change properties. When you're done, Select "Save as..." from the File menu.
You can exit the program through the X knob on the program window, or through the "Quit" command from the File menu.
22 changes: 12 additions & 10 deletions imaging/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
find_package( OpenCV REQUIRED )

add_library( pixelator
Pixelator.cpp
Pixelator.h
Expand All @@ -11,16 +12,17 @@ add_library( imaging
ShrinkPixelator.cpp
ShrinkPixelator.h
)
target_include_directories( imaging PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
target_include_directories( imaging PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( imaging PUBLIC ${OpenCV_LIBS} pixelator)
target_compile_definitions(imaging PRIVATE -DDOCTEST_CONFIG_DISABLE)

add_executable( pixelator_test Pixelator.cpp )
target_include_directories( pixelator_test PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
target_link_libraries( pixelator_test PUBLIC ${OpenCV_LIBS} )
target_compile_definitions( pixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
if(DOCTEST_INCLUDE_DIR)
add_executable( test_pixelator Pixelator.cpp )
target_include_directories( test_pixelator PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
target_link_libraries( test_pixelator PUBLIC ${OpenCV_LIBS} )
target_compile_definitions( test_pixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )

add_executable( shrink_pixelator_test ShrinkPixelator.cpp )
target_include_directories( shrink_pixelator_test PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
target_link_libraries( shrink_pixelator_test PUBLIC ${OpenCV_LIBS} pixelator)
target_compile_definitions( shrink_pixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DIGNORE_BASE_CLASS_TESTS)
add_executable( test_shrink_pixelator ShrinkPixelator.cpp )
target_include_directories( test_shrink_pixelator PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
target_link_libraries( test_shrink_pixelator PUBLIC ${OpenCV_LIBS} pixelator utilities )
target_compile_definitions( test_shrink_pixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DIGNORE_BASE_CLASS_TESTS)
endif()
4 changes: 2 additions & 2 deletions imaging/Pixelator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace imaging
}
bool Pixelator::prepare()
{
add_color(PixelValue(255, 255, 255));
add_color(PixelValue(0, 0, 255));
add_color(PixelValue());
return true;
}
Expand Down Expand Up @@ -120,7 +120,7 @@ namespace
return std::sqrt(std::pow(p1.rc - p2.rc, 2) + std::pow(p1.pl - p2.pl, 2) + std::pow(p1.v - p2.v, 2));
}
}
#ifndef IGNORE_BASE_CLASS_TESTS
#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && (! defined(IGNORE_BASE_CLASS_TESTS))
#include <doctest.h>

class ColorTestPixelator : public imaging::Pixelator
Expand Down
201 changes: 18 additions & 183 deletions imaging/ShrinkPixelator.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "ShrinkPixelator.h"
#include "calculus.h"
#include <optional>

namespace
{
unsigned least_common_multiple(unsigned a, unsigned b);
}
#include <set>

namespace imaging
{
Expand All @@ -17,7 +14,7 @@ namespace imaging
int rows_backup = pictureBuffer.rows;
int columns_backup = pictureBuffer.cols;
double aspect_ratio_old = (rows_backup * 1.) / columns_backup;
unsigned stixelLcm{ least_common_multiple(gauge_rows, gauge_stitches) };
unsigned stixelLcm{ calculus::least_common_multiple(gauge_rows, gauge_stitches) };
unsigned stitchWidth = stixelLcm / gauge_stitches;
unsigned stitchHeight = stixelLcm / gauge_rows;
unsigned stitchCount = (unsigned)(ceil((in_width / 10.) * gauge_stitches));
Expand Down Expand Up @@ -87,183 +84,21 @@ namespace imaging
}
}

namespace
{
std::optional<unsigned> divides(unsigned number, unsigned divisor)
{
unsigned result = number / divisor;
if (result * divisor == number) return result;
return std::optional<unsigned>{};
}

std::vector<unsigned> divisors(unsigned x)
{
std::vector<unsigned> divisors;
for (unsigned factor = 1; factor < sqrt(x); ++factor)
{
auto result = divides(x, factor);
if (result.has_value())
{
divisors.push_back(factor);
divisors.push_back(result.value());
}
}
return divisors;
}

unsigned search_greatest_common_divisor(unsigned smallerValue, unsigned largerValue)
{
auto divisors_smaller = divisors(smallerValue);
auto divisors_larger = divisors(largerValue);
std::sort(divisors_smaller.begin(), divisors_smaller.end(), [](unsigned one, unsigned other) { return other < one; });
std::sort(divisors_larger.begin(), divisors_larger.end());
for (auto divisor : divisors_smaller)
{
if (std::find(divisors_larger.begin(), divisors_larger.end(), divisor) != divisors_larger.end()) return divisor;
}
return 1;
}

unsigned greatest_common_divisor(unsigned a, unsigned b)
{
if (a == b) return a;
if (a < b)
{
if (divides(b, a).has_value()) return a;
return search_greatest_common_divisor(a, b);
}
else
{
if (divides(a, b).has_value()) return b;
return search_greatest_common_divisor(b, a);
}
}

unsigned least_common_multiple(unsigned a, unsigned b)
{
unsigned gcd = greatest_common_divisor(a, b);
return a * b / gcd; //gcd * (a / gcd) * (b / gcd);
}
}

#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest.h>

TEST_CASE("test divides") {
auto result = divides(25, 3);
CHECK(!result.has_value());

result = divides(24, 3);
CHECK(result.has_value());
CHECK_EQ(result.value(), 8);

result = divides(3, 3);
CHECK(result.has_value());
CHECK_EQ(result.value(), 1);

result = divides(16, 4);
CHECK(result.has_value());
CHECK_EQ(result.value(), 4);

result = divides(0, 2);
CHECK(result.has_value());
CHECK_EQ(result.value(), 0);

result = divides(5, 0);
CHECK(result.has_value());
CHECK_EQ(result.value(), 0);

result = divides(0, 0);
CHECK(result.has_value());
CHECK_EQ(result.value(), 0);

result = divides(1953125, 16777216);
CHECK(!result.has_value());
}

TEST_CASE("test divisors") {
std::vector<unsigned> result;

result = divisors(5);
CHECK_EQ(result.size(), 2);
CHECK_NE(std::find(result.begin(), result.end(), 1), result.end());
CHECK_NE(std::find(result.begin(), result.end(),5), result.end());

result = divisors(16);
CHECK_EQ(result.size(), 5);
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
CHECK_NE(std::find(result.begin(), result.end(),8), result.end());
CHECK_NE(std::find(result.begin(), result.end(),16), result.end());

result = divisors(24);
CHECK_EQ(result.size(), 8);
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
CHECK_NE(std::find(result.begin(), result.end(),3), result.end());
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
CHECK_NE(std::find(result.begin(), result.end(),6), result.end());
CHECK_NE(std::find(result.begin(), result.end(),8), result.end());
CHECK_NE(std::find(result.begin(), result.end(),12), result.end());
CHECK_NE(std::find(result.begin(), result.end(),24), result.end());

result = divisors(36);
CHECK_EQ(result.size(), 9);
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
CHECK_NE(std::find(result.begin(), result.end(),3), result.end());
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
CHECK_NE(std::find(result.begin(), result.end(),6), result.end());
CHECK_NE(std::find(result.begin(), result.end(),9), result.end());
CHECK_NE(std::find(result.begin(), result.end(),12), result.end());
CHECK_NE(std::find(result.begin(), result.end(),18), result.end());
CHECK_NE(std::find(result.begin(), result.end(),36), result.end());

result = divisors(8192); // 2^13
CHECK_EQ(result.size(), 14);
unsigned factorInlist = 1;
for (auto i = 0; i < 14; ++i)
{
CHECK_NE(std::find(result.begin(), result.end(),factorInlist), result.end());
factorInlist *= 2;
}

result = divisors(262144); // 2^18
CHECK_EQ(result.size(), 19);
factorInlist = 1;
for (auto i = 0; i < 19; ++i)
{
CHECK_NE(std::find(result.begin(), result.end(),factorInlist), result.end());
factorInlist *= 2;
}
}

TEST_CASE("test search_greatest_common_divisor") {
CHECK_EQ(search_greatest_common_divisor(5, 3), 1);
CHECK_EQ(search_greatest_common_divisor(59049, 262144), 1);
CHECK_EQ(search_greatest_common_divisor(1024, 262144), 1024);
CHECK_EQ(search_greatest_common_divisor(24, 36), 12);
CHECK_EQ(search_greatest_common_divisor(0, 36), 0);
}

TEST_CASE("test greatest_common_divisor") {
CHECK_EQ(greatest_common_divisor(5, 3), 1);
CHECK_EQ(greatest_common_divisor(262144, 59049), 1);
CHECK_EQ(greatest_common_divisor(262144, 1024), 1024);
CHECK_EQ(greatest_common_divisor(1024, 262144), 1024);
CHECK_EQ(greatest_common_divisor(36, 24), 12);
CHECK_EQ(greatest_common_divisor(36, 0), 0);
class TestShrinkPixelator : public imaging::ShrinkPixelator
{
public:
TestShrinkPixelator() : ShrinkPixelator("") { pictureBuffer = ImgData(200, 200, CV_8UC3, cv::Scalar{ 60, 0, 255, 0 }); }
auto pictureColumns() const { return pictureBuffer.cols; }
auto pictureRows() const { return pictureBuffer.rows; }
};

TEST_CASE("test pixelation") {
TestShrinkPixelator pixor;
pixor.prepare();
CHECK(pixor.to_pixels(8, 8, 20, 18, one_bit::CropRegion::CENTER));
MESSAGE("This is a stub. The class must be rewritten in order to be unit testable properly.");
}

TEST_CASE("test pair-based least_common_multiple") {
CHECK_EQ(least_common_multiple(16, 8), 16);
CHECK_EQ(least_common_multiple(8, 16), 16);
CHECK_EQ(least_common_multiple(16, 18), 144);
CHECK_EQ(least_common_multiple(1, 15), 15);
CHECK_EQ(least_common_multiple(36, 24), 72);
CHECK_EQ(least_common_multiple(243, 1024), 243 * 1024); // power of 3 and power of 2 don't share factors

CHECK_EQ(least_common_multiple(0, 18), 0);
CHECK_EQ(least_common_multiple(18, 0), 0);
CHECK_EQ(least_common_multiple(0, 0), 0);
}
#endif
30 changes: 15 additions & 15 deletions qtgui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ add_library( qtgui
SourceImage.h
SourceImage.cpp
)
target_include_directories( qtgui PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR})

target_include_directories( qtgui PRIVATE ${Qt5_DIR})
target_link_libraries( qtgui PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
target_include_directories( qtgui PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( qtgui PUBLIC utilities )
target_compile_definitions( qtgui PRIVATE -DDOCTEST_CONFIG_DISABLE )
qtquick_compiler_add_resources( RESOURCES resources/one-bit-resources.qrc )

add_executable( qtpixelator_test QtPixelator.cpp )
target_include_directories( qtpixelator_test PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR} )
target_link_libraries( qtpixelator_test PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
target_include_directories( qtpixelator_test PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( qtpixelator_test PUBLIC utilities )
target_compile_definitions( qtpixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )

add_executable( source_image_test SourceImage.cpp )
target_include_directories( source_image_test PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR} )
target_link_libraries( source_image_test PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
target_include_directories( source_image_test PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( source_image_test PUBLIC utilities )
target_compile_definitions( source_image_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
if(DOCTEST_INCLUDE_DIR)
add_executable( test_qtpixelator QtPixelator.cpp )
target_include_directories( test_qtpixelator PRIVATE ${Qt5_DIR} "${DOCTEST_INCLUDE_DIR}" )
target_include_directories( test_qtpixelator PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( test_qtpixelator PUBLIC Qt5::Core Qt5::Gui utilities )
target_compile_definitions( test_qtpixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
add_executable( test_source_image SourceImage.cpp )
target_include_directories( test_source_image PRIVATE ${Qt5_DIR} "${DOCTEST_INCLUDE_DIR}" )
target_include_directories( test_source_image PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
target_link_libraries( test_source_image PUBLIC Qt5::Gui Qt5::Quick utilities )
target_compile_definitions( test_source_image PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
endif()
Loading

0 comments on commit dfbd637

Please sign in to comment.