Skip to content

Commit dfbd637

Browse files
authored
Merge pull request #33 from starturtle/13-refactor-logging
#13 / #31
2 parents 9a4714d + c4e70a4 commit dfbd637

18 files changed

+580
-571
lines changed

CMakeLists.txt

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ endif()
2828
# executable setup
2929
add_executable( one_bit main.cpp script_mode.h script_mode.cpp ${RESOURCES})
3030

31-
target_include_directories( one_bit PUBLIC ${OpenCV_INCLUDE_DIRS} ${DOCTEST_INCLUDE_DIR})
31+
target_include_directories( one_bit PUBLIC ${OpenCV_INCLUDE_DIRS})
3232
target_link_libraries( one_bit PUBLIC imaging utilities ${OpenCV_LIBS} )
3333
if( ${USE_QT5} )
34+
message(STATUS "build with GUI mode")
3435
target_include_directories( one_bit PUBLIC ${Qt5_DIR} )
3536
target_link_libraries( one_bit PUBLIC qtgui Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
3637
endif()
3738

38-
enable_testing()
39+
if(DOCTEST_INCLUDE_DIR)
40+
message(STATUS "build test projects")
41+
enable_testing()
42+
endif()
3943

README.md

+38-2
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,50 @@ This tool is aimed at people crafting with thread, in the more literal sense of
55
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.
66
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.
77

8+
## Installation
9+
10+
### Requirements
11+
Disclaimer: I have not made any experiments wrt backwards compatibility yet.
12+
#### OpenCV
13+
This project requires [OpenCV](https://opencv.org/releases/) to be built. I'm working with version 4.2.
14+
The opencv root folder must be part of your CMAKE_MODULE_PATH.
15+
#### CMake
16+
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.
17+
#### Qt5
18+
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.
19+
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.
20+
#### Doctest
21+
Testing requires [doctest](https://github.com/onqtam/doctest) on your machine. Only for running the program, it is not necessary.
22+
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.
23+
824
## Automatic generation
925

1026
### Input Data
1127
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.
1228

13-
### Recomputation
29+
### Shell Mode
1430
From the gauge and result size, the amount and dimensions of one "stixel" (stitch-sized pixel) can directly be computed.
1531
The algorithm will then determine the overall color of that stixel in original colors, then figure out which of the yarn colors comes closest.
32+
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:
33+
* TOP_LEFT
34+
* TOP
35+
* TOP_RIGHT
36+
* LEFT
37+
* CENTER
38+
* RIGHT
39+
* BOTTOM_LEFT
40+
* BOTTOM
41+
* BOTTOM_RIGHT
42+
1643
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.
1744

18-
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.
45+
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.
46+
47+
### GUI Mode
48+
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.
49+
Use the text input fields to determine gauge size and desired output size of your workpiece. The preview will adapt.
50+
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.
51+
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.
52+
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.
53+
The preview will adjust each time you change properties. When you're done, Select "Save as..." from the File menu.
54+
You can exit the program through the X knob on the program window, or through the "Quit" command from the File menu.

imaging/CMakeLists.txt

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
find_package( OpenCV REQUIRED )
2+
23
add_library( pixelator
34
Pixelator.cpp
45
Pixelator.h
@@ -11,16 +12,17 @@ add_library( imaging
1112
ShrinkPixelator.cpp
1213
ShrinkPixelator.h
1314
)
14-
target_include_directories( imaging PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
15+
target_include_directories( imaging PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities )
1516
target_link_libraries( imaging PUBLIC ${OpenCV_LIBS} pixelator)
16-
target_compile_definitions(imaging PRIVATE -DDOCTEST_CONFIG_DISABLE)
1717

18-
add_executable( pixelator_test Pixelator.cpp )
19-
target_include_directories( pixelator_test PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
20-
target_link_libraries( pixelator_test PUBLIC ${OpenCV_LIBS} )
21-
target_compile_definitions( pixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
18+
if(DOCTEST_INCLUDE_DIR)
19+
add_executable( test_pixelator Pixelator.cpp )
20+
target_include_directories( test_pixelator PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
21+
target_link_libraries( test_pixelator PUBLIC ${OpenCV_LIBS} )
22+
target_compile_definitions( test_pixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
2223

23-
add_executable( shrink_pixelator_test ShrinkPixelator.cpp )
24-
target_include_directories( shrink_pixelator_test PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
25-
target_link_libraries( shrink_pixelator_test PUBLIC ${OpenCV_LIBS} pixelator)
26-
target_compile_definitions( shrink_pixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DIGNORE_BASE_CLASS_TESTS)
24+
add_executable( test_shrink_pixelator ShrinkPixelator.cpp )
25+
target_include_directories( test_shrink_pixelator PUBLIC ${OpenCV_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/utilities "${DOCTEST_INCLUDE_DIR}" )
26+
target_link_libraries( test_shrink_pixelator PUBLIC ${OpenCV_LIBS} pixelator utilities )
27+
target_compile_definitions( test_shrink_pixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DIGNORE_BASE_CLASS_TESTS)
28+
endif()

imaging/Pixelator.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace imaging
1818
}
1919
bool Pixelator::prepare()
2020
{
21-
add_color(PixelValue(255, 255, 255));
21+
add_color(PixelValue(0, 0, 255));
2222
add_color(PixelValue());
2323
return true;
2424
}
@@ -120,7 +120,7 @@ namespace
120120
return std::sqrt(std::pow(p1.rc - p2.rc, 2) + std::pow(p1.pl - p2.pl, 2) + std::pow(p1.v - p2.v, 2));
121121
}
122122
}
123-
#ifndef IGNORE_BASE_CLASS_TESTS
123+
#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && (! defined(IGNORE_BASE_CLASS_TESTS))
124124
#include <doctest.h>
125125

126126
class ColorTestPixelator : public imaging::Pixelator

imaging/ShrinkPixelator.cpp

+18-183
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
#include "ShrinkPixelator.h"
2+
#include "calculus.h"
23
#include <optional>
3-
4-
namespace
5-
{
6-
unsigned least_common_multiple(unsigned a, unsigned b);
7-
}
4+
#include <set>
85

96
namespace imaging
107
{
@@ -17,7 +14,7 @@ namespace imaging
1714
int rows_backup = pictureBuffer.rows;
1815
int columns_backup = pictureBuffer.cols;
1916
double aspect_ratio_old = (rows_backup * 1.) / columns_backup;
20-
unsigned stixelLcm{ least_common_multiple(gauge_rows, gauge_stitches) };
17+
unsigned stixelLcm{ calculus::least_common_multiple(gauge_rows, gauge_stitches) };
2118
unsigned stitchWidth = stixelLcm / gauge_stitches;
2219
unsigned stitchHeight = stixelLcm / gauge_rows;
2320
unsigned stitchCount = (unsigned)(ceil((in_width / 10.) * gauge_stitches));
@@ -87,183 +84,21 @@ namespace imaging
8784
}
8885
}
8986

90-
namespace
91-
{
92-
std::optional<unsigned> divides(unsigned number, unsigned divisor)
93-
{
94-
unsigned result = number / divisor;
95-
if (result * divisor == number) return result;
96-
return std::optional<unsigned>{};
97-
}
98-
99-
std::vector<unsigned> divisors(unsigned x)
100-
{
101-
std::vector<unsigned> divisors;
102-
for (unsigned factor = 1; factor < sqrt(x); ++factor)
103-
{
104-
auto result = divides(x, factor);
105-
if (result.has_value())
106-
{
107-
divisors.push_back(factor);
108-
divisors.push_back(result.value());
109-
}
110-
}
111-
return divisors;
112-
}
113-
114-
unsigned search_greatest_common_divisor(unsigned smallerValue, unsigned largerValue)
115-
{
116-
auto divisors_smaller = divisors(smallerValue);
117-
auto divisors_larger = divisors(largerValue);
118-
std::sort(divisors_smaller.begin(), divisors_smaller.end(), [](unsigned one, unsigned other) { return other < one; });
119-
std::sort(divisors_larger.begin(), divisors_larger.end());
120-
for (auto divisor : divisors_smaller)
121-
{
122-
if (std::find(divisors_larger.begin(), divisors_larger.end(), divisor) != divisors_larger.end()) return divisor;
123-
}
124-
return 1;
125-
}
126-
127-
unsigned greatest_common_divisor(unsigned a, unsigned b)
128-
{
129-
if (a == b) return a;
130-
if (a < b)
131-
{
132-
if (divides(b, a).has_value()) return a;
133-
return search_greatest_common_divisor(a, b);
134-
}
135-
else
136-
{
137-
if (divides(a, b).has_value()) return b;
138-
return search_greatest_common_divisor(b, a);
139-
}
140-
}
141-
142-
unsigned least_common_multiple(unsigned a, unsigned b)
143-
{
144-
unsigned gcd = greatest_common_divisor(a, b);
145-
return a * b / gcd; //gcd * (a / gcd) * (b / gcd);
146-
}
147-
}
148-
87+
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
14988
#include <doctest.h>
150-
151-
TEST_CASE("test divides") {
152-
auto result = divides(25, 3);
153-
CHECK(!result.has_value());
154-
155-
result = divides(24, 3);
156-
CHECK(result.has_value());
157-
CHECK_EQ(result.value(), 8);
158-
159-
result = divides(3, 3);
160-
CHECK(result.has_value());
161-
CHECK_EQ(result.value(), 1);
162-
163-
result = divides(16, 4);
164-
CHECK(result.has_value());
165-
CHECK_EQ(result.value(), 4);
166-
167-
result = divides(0, 2);
168-
CHECK(result.has_value());
169-
CHECK_EQ(result.value(), 0);
170-
171-
result = divides(5, 0);
172-
CHECK(result.has_value());
173-
CHECK_EQ(result.value(), 0);
174-
175-
result = divides(0, 0);
176-
CHECK(result.has_value());
177-
CHECK_EQ(result.value(), 0);
178-
179-
result = divides(1953125, 16777216);
180-
CHECK(!result.has_value());
181-
}
182-
183-
TEST_CASE("test divisors") {
184-
std::vector<unsigned> result;
185-
186-
result = divisors(5);
187-
CHECK_EQ(result.size(), 2);
188-
CHECK_NE(std::find(result.begin(), result.end(), 1), result.end());
189-
CHECK_NE(std::find(result.begin(), result.end(),5), result.end());
190-
191-
result = divisors(16);
192-
CHECK_EQ(result.size(), 5);
193-
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
194-
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
195-
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
196-
CHECK_NE(std::find(result.begin(), result.end(),8), result.end());
197-
CHECK_NE(std::find(result.begin(), result.end(),16), result.end());
198-
199-
result = divisors(24);
200-
CHECK_EQ(result.size(), 8);
201-
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
202-
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
203-
CHECK_NE(std::find(result.begin(), result.end(),3), result.end());
204-
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
205-
CHECK_NE(std::find(result.begin(), result.end(),6), result.end());
206-
CHECK_NE(std::find(result.begin(), result.end(),8), result.end());
207-
CHECK_NE(std::find(result.begin(), result.end(),12), result.end());
208-
CHECK_NE(std::find(result.begin(), result.end(),24), result.end());
209-
210-
result = divisors(36);
211-
CHECK_EQ(result.size(), 9);
212-
CHECK_NE(std::find(result.begin(), result.end(),1), result.end());
213-
CHECK_NE(std::find(result.begin(), result.end(),2), result.end());
214-
CHECK_NE(std::find(result.begin(), result.end(),3), result.end());
215-
CHECK_NE(std::find(result.begin(), result.end(),4), result.end());
216-
CHECK_NE(std::find(result.begin(), result.end(),6), result.end());
217-
CHECK_NE(std::find(result.begin(), result.end(),9), result.end());
218-
CHECK_NE(std::find(result.begin(), result.end(),12), result.end());
219-
CHECK_NE(std::find(result.begin(), result.end(),18), result.end());
220-
CHECK_NE(std::find(result.begin(), result.end(),36), result.end());
221-
222-
result = divisors(8192); // 2^13
223-
CHECK_EQ(result.size(), 14);
224-
unsigned factorInlist = 1;
225-
for (auto i = 0; i < 14; ++i)
226-
{
227-
CHECK_NE(std::find(result.begin(), result.end(),factorInlist), result.end());
228-
factorInlist *= 2;
229-
}
230-
231-
result = divisors(262144); // 2^18
232-
CHECK_EQ(result.size(), 19);
233-
factorInlist = 1;
234-
for (auto i = 0; i < 19; ++i)
235-
{
236-
CHECK_NE(std::find(result.begin(), result.end(),factorInlist), result.end());
237-
factorInlist *= 2;
238-
}
239-
}
240-
241-
TEST_CASE("test search_greatest_common_divisor") {
242-
CHECK_EQ(search_greatest_common_divisor(5, 3), 1);
243-
CHECK_EQ(search_greatest_common_divisor(59049, 262144), 1);
244-
CHECK_EQ(search_greatest_common_divisor(1024, 262144), 1024);
245-
CHECK_EQ(search_greatest_common_divisor(24, 36), 12);
246-
CHECK_EQ(search_greatest_common_divisor(0, 36), 0);
247-
}
248-
249-
TEST_CASE("test greatest_common_divisor") {
250-
CHECK_EQ(greatest_common_divisor(5, 3), 1);
251-
CHECK_EQ(greatest_common_divisor(262144, 59049), 1);
252-
CHECK_EQ(greatest_common_divisor(262144, 1024), 1024);
253-
CHECK_EQ(greatest_common_divisor(1024, 262144), 1024);
254-
CHECK_EQ(greatest_common_divisor(36, 24), 12);
255-
CHECK_EQ(greatest_common_divisor(36, 0), 0);
89+
class TestShrinkPixelator : public imaging::ShrinkPixelator
90+
{
91+
public:
92+
TestShrinkPixelator() : ShrinkPixelator("") { pictureBuffer = ImgData(200, 200, CV_8UC3, cv::Scalar{ 60, 0, 255, 0 }); }
93+
auto pictureColumns() const { return pictureBuffer.cols; }
94+
auto pictureRows() const { return pictureBuffer.rows; }
95+
};
96+
97+
TEST_CASE("test pixelation") {
98+
TestShrinkPixelator pixor;
99+
pixor.prepare();
100+
CHECK(pixor.to_pixels(8, 8, 20, 18, one_bit::CropRegion::CENTER));
101+
MESSAGE("This is a stub. The class must be rewritten in order to be unit testable properly.");
256102
}
257103

258-
TEST_CASE("test pair-based least_common_multiple") {
259-
CHECK_EQ(least_common_multiple(16, 8), 16);
260-
CHECK_EQ(least_common_multiple(8, 16), 16);
261-
CHECK_EQ(least_common_multiple(16, 18), 144);
262-
CHECK_EQ(least_common_multiple(1, 15), 15);
263-
CHECK_EQ(least_common_multiple(36, 24), 72);
264-
CHECK_EQ(least_common_multiple(243, 1024), 243 * 1024); // power of 3 and power of 2 don't share factors
265-
266-
CHECK_EQ(least_common_multiple(0, 18), 0);
267-
CHECK_EQ(least_common_multiple(18, 0), 0);
268-
CHECK_EQ(least_common_multiple(0, 0), 0);
269-
}
104+
#endif

qtgui/CMakeLists.txt

+15-15
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@ add_library( qtgui
1111
SourceImage.h
1212
SourceImage.cpp
1313
)
14-
target_include_directories( qtgui PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR})
14+
15+
target_include_directories( qtgui PRIVATE ${Qt5_DIR})
1516
target_link_libraries( qtgui PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
1617
target_include_directories( qtgui PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
1718
target_link_libraries( qtgui PUBLIC utilities )
18-
target_compile_definitions( qtgui PRIVATE -DDOCTEST_CONFIG_DISABLE )
1919
qtquick_compiler_add_resources( RESOURCES resources/one-bit-resources.qrc )
2020

21-
add_executable( qtpixelator_test QtPixelator.cpp )
22-
target_include_directories( qtpixelator_test PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR} )
23-
target_link_libraries( qtpixelator_test PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
24-
target_include_directories( qtpixelator_test PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
25-
target_link_libraries( qtpixelator_test PUBLIC utilities )
26-
target_compile_definitions( qtpixelator_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
27-
28-
add_executable( source_image_test SourceImage.cpp )
29-
target_include_directories( source_image_test PRIVATE ${Qt5_DIR} ${DOCTEST_INCLUDE_DIR} )
30-
target_link_libraries( source_image_test PUBLIC Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Network )
31-
target_include_directories( source_image_test PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
32-
target_link_libraries( source_image_test PUBLIC utilities )
33-
target_compile_definitions( source_image_test PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
21+
if(DOCTEST_INCLUDE_DIR)
22+
add_executable( test_qtpixelator QtPixelator.cpp )
23+
target_include_directories( test_qtpixelator PRIVATE ${Qt5_DIR} "${DOCTEST_INCLUDE_DIR}" )
24+
target_include_directories( test_qtpixelator PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
25+
target_link_libraries( test_qtpixelator PUBLIC Qt5::Core Qt5::Gui utilities )
26+
target_compile_definitions( test_qtpixelator PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
27+
28+
add_executable( test_source_image SourceImage.cpp )
29+
target_include_directories( test_source_image PRIVATE ${Qt5_DIR} "${DOCTEST_INCLUDE_DIR}" )
30+
target_include_directories( test_source_image PUBLIC ${CMAKE_SOURCE_DIR}/utilities )
31+
target_link_libraries( test_source_image PUBLIC Qt5::Gui Qt5::Quick utilities )
32+
target_compile_definitions( test_source_image PRIVATE -DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN )
33+
endif()

0 commit comments

Comments
 (0)