Skip to content

Commit

Permalink
Implementation of lanczos down scaling (#309)
Browse files Browse the repository at this point in the history
* Simple implementation of lanczos scaling

* Simple implementation of lanczos scaling

* Refactor lanczos into separate header

This commit moves the algorithm
into its own header. It also provides
templated interface now, utilizing
a little bit of decltype and declval
to resolve types for lambdas and stuff

* Zero pixel at start of lanczos_at

This commit fixes a possible bug and
fixes some style incosistencies

* Improve documentation for Lanczos

This commit adds documentation which
describes when to use the algorithm,
a brief description of how it is
supposed to work, and some caution
on the quality of the output

* Address style issues and fix warnings

This commit fixes a style issue realted
to namespace declaration and
fixes a warning in added zeroing of
a pixel at start of lanczos

* text-realign function arguments

* fix formatting issues

* Implement handful of sanity tests

This commit introduces a couple of
sanity tests such as black image
scaling to black image, and lanczos
response being 0 at x = 0

* bracket on newline for for loops

* add lanczos scaling to tests

* more precision in lanczos calculation

This commit migrates integral values
to double precision and uses PI
provided by boost.Math. These changes
solve downscaled image being darker

* Simple implementation of lanczos scaling

* Refactor lanczos into separate header

This commit moves the algorithm
into its own header. It also provides
templated interface now, utilizing
a little bit of decltype and declval
to resolve types for lambdas and stuff

* Zero pixel at start of lanczos_at

This commit fixes a possible bug and
fixes some style incosistencies

* Improve documentation for Lanczos

This commit adds documentation which
describes when to use the algorithm,
a brief description of how it is
supposed to work, and some caution
on the quality of the output

* Address style issues and fix warnings

This commit fixes a style issue realted
to namespace declaration and
fixes a warning in added zeroing of
a pixel at start of lanczos

* text-realign function arguments

* fix formatting issues

* Implement handful of sanity tests

This commit introduces a couple of
sanity tests such as black image
scaling to black image, and lanczos
response being 0 at x = 0

* bracket on newline for for loops

* add lanczos scaling to tests

* more precision in lanczos calculation

This commit migrates integral values
to double precision and uses PI
provided by boost.Math. These changes
solve downscaled image being darker

* Add Jamfile for ip test directory

Add Jamfile for image_processing
test directory and build-project
from outer test directory

* Add IP test directory to ci build

This commit adds a line at the end
of .ci/build-and-test.sh to include
image processing tests in CI builds

* Remove redundant lines from Jamfile

Simplify Jamfile at
test/image_processing

* Rewrite range condition

x > -a && x < a exchange with
-a < x && x < a in lanczos

* Add newline at the end of files

* Add math and lexical_cast to get-boost

As math and lexical_cast are used
in lanczos scaling, both were added
to get-boost

* Revert "Add newline at the end of files"

Since Boost.Math.Constants is
overengineered for use case by
requiring Boost.lexical_cast or
a global define, it is removed

This reverts commit 0743ab0.

* Define pi and use in lanczos

Value of pi is moved out into detail
and used by lanczos

* Move image_processing to test/core

* formatting fixes

* Adjust CMakeLists for moved IP tests

This commit removes image_processing as
subdirectory from test/ and adds to
test/core's CMakeLists

* Remove unused from get-boost

Since lanczos scaling no longer uses
boost.math.constants, lexical_cast
has been removed as well

* Downgrade math to transitive dep

This is a stray change left from
my incorrect resetting of the HEAD
on lanczos branch

* Remove unnecessary includes

io includes are not used in lanczos
scaling test, and they break build

* Fix ambiguous overload issue for min

Since width() and height()  now return
std::ptrdiff_t, call to std::min needs
a cast. There was also shadowing of
pi declared in detail/math.hpp,
which is also fixed

* Apply mloskot's patch

The patch provided by Mateusz changes
all usages of long int into ptrdiff_t,
which is returned by width() and
height() functions of image_view

* Apply .editorconfig rules

This commit is a simple reformat of
affected files

* Use aliases x_coord_t and y_coord_t

The change converges integer handling
in arguments, using view' type aliases.
View arguments have to come first to
avoid non-deduced context problem.
Also replaced long int with ptrdiff_t
in lanczos, in numeric.hpp file

* Apply alias usage for rest of the code

Some places with ptrdiff_t were
left out from previous commit, so they
are changed in this one. Plus a fix for
max call being ambiguous.

* Replace all literals with casted vars

This commit takes extreme stance of
never using an integer literal due to
problems in ambiguity of deduction of
min and max functions

* Fix unenclosed foreach

During rebase of gsoc2019 onto develop,
I didn't enclose first foreach with
matching endforeach, hence tests failed
  • Loading branch information
simmplecoder committed Jun 23, 2019
1 parent 716fe9a commit cf897c5
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 0 deletions.
3 changes: 3 additions & 0 deletions include/boost/gil/detail/math.hpp
@@ -0,0 +1,3 @@
namespace boost{ namespace gil{
static constexpr double pi = 3.14159265358979323846;
}}
43 changes: 43 additions & 0 deletions include/boost/gil/image_processing/numeric.hpp
@@ -0,0 +1,43 @@
#include <boost/gil/detail/math.hpp>
#include <cmath>

namespace boost{ namespace gil{

/// \defgroup ImageProcessingMath
/// \brief Math operations for IP algorithms
///
/// This is mostly handful of mathemtical
/// operations that are required by other
/// image processing algorithms

/// \brief Normalized cardinal sine
/// \ingroup ImageProcessingMath
///
/// normalized_sinc(x) = sin(pi * x) / (pi * x)
double normalized_sinc(double x)
{
return std::sin(x * boost::gil::pi) / (x * boost::gil::pi);
}

/// \brief Lanczos response at point x
/// \ingroup ImageProcessingMath
///
/// Lanczos response is defined as:
/// x == 0: 1
/// -a < x && x < a: 0
/// otherwise: normalized_sinc(x) / normalized_sinc(x / a)
double lanczos(double x, std::ptrdiff_t a)
{
if (x == 0)
{
return 1;
}
if (-a < x && x < a)
{
return normalized_sinc(x)
/ normalized_sinc(x / static_cast<double>(a));
}

return 0;
}
}}
121 changes: 121 additions & 0 deletions include/boost/gil/image_processing/scaling.hpp
@@ -0,0 +1,121 @@
#include <boost/gil/image_view.hpp>
#include <boost/gil/rgb.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/image_processing/numeric.hpp>

namespace boost{ namespace gil{
/// \defgroup ScalingAlgorithms
/// \brief Algorthims suitable for rescaling
///
/// These algorithms are used to improve image
/// quality after image resizing is made.

/// \defgroup DownScalingAlgorithms
/// \ingroup ScalingAlgorithms
/// \brief Algorthims suitable for downscaling
///
/// These algorithms provide best results when used
/// for downscaling. Using for upscaling will probably
/// provide less than good results.


/// \brief a single step of lanczos downscaling
/// \ingroup DownScalingAlgorithms
///
/// Use this algorithm to scale down source image
/// into a smaller image with reasonable quality.
/// Do note that having a look at the output once
/// is a good idea, since it might have ringing
/// artifacts.
template <typename ImageView>
void lanczos_at(
ImageView input_view,
ImageView output_view,
typename ImageView::x_coord_t source_x,
typename ImageView::y_coord_t source_y,
typename ImageView::x_coord_t target_x,
typename ImageView::y_coord_t target_y,
std::ptrdiff_t a)
{
using x_coord_t = typename ImageView::x_coord_t;
using y_coord_t = typename ImageView::y_coord_t;
using pixel_t = typename std::remove_reference<
decltype(std::declval<ImageView>()(0, 0))
>::type;
// C++11 doesn't allow auto in lambdas
using channel_t = typename std::remove_reference<
decltype(
std::declval<pixel_t>().at(
std::integral_constant<int, 0>{}
)
)
>::type;
pixel_t result_pixel;
boost::gil::static_transform(result_pixel, result_pixel,
[](channel_t) { return static_cast<channel_t>(0); });
auto x_zero = static_cast<x_coord_t>(0);
auto x_one = static_cast<x_coord_t>(1);
auto y_zero = static_cast<y_coord_t>(0);
auto y_one = static_cast<y_coord_t>(1);

for (y_coord_t y_i = std::max(source_y - static_cast<y_coord_t>(a) + y_one, y_zero);
y_i <= std::min(source_y + static_cast<y_coord_t>(a), input_view.height() - y_one);
++y_i)
{
for (x_coord_t x_i = std::max(source_x - static_cast<x_coord_t>(a) + x_one, x_zero);
x_i <= std::min(source_x + static_cast<x_coord_t>(a), input_view.width() - x_one);
++x_i)
{
double lanczos_response = boost::gil::lanczos(source_x - x_i, a)
* boost::gil::lanczos(source_y - y_i, a);
auto op = [lanczos_response](channel_t prev, channel_t next)
{
return static_cast<channel_t>(prev + next * lanczos_response);
};
boost::gil::static_transform(result_pixel,
input_view(source_x, source_y),
result_pixel,
op);
}
}

output_view(target_x, target_y) = result_pixel;
}

/// \brief Complete Lanczos algorithm
/// \ingroup DownScalingAlgorithms
///
/// This algorithm does full pass over
/// resulting image and convolves pixels from
/// original image. Do note that it might be a good
/// idea to have a look at test output as there
/// might be ringing artifacts.
/// Based on wikipedia article:
/// https://en.wikipedia.org/wiki/Lanczos_resampling
/// with standardinzed cardinal sin (sinc)
template <typename ImageView>
void scale_lanczos(ImageView input_view, ImageView output_view, std::ptrdiff_t a)
{
double scale_x = (static_cast<double>(output_view.width()))
/ static_cast<double>(input_view.width());
double scale_y = (static_cast<double>(output_view.height()))
/ static_cast<double>(input_view.height());

using x_coord_t = typename ImageView::x_coord_t;
using y_coord_t = typename ImageView::y_coord_t;
for (y_coord_t y = 0; y < output_view.height(); ++y)
{
for (x_coord_t x = 0; x < output_view.width(); ++x)
{
boost::gil::lanczos_at(
input_view,
output_view,
x / scale_x,
y / scale_y,
x,
y,
a);
}
}
}
}}
20 changes: 20 additions & 0 deletions test/core/image_processing/CMakeLists.txt
Expand Up @@ -24,3 +24,23 @@ foreach(_name
unset(_name)
unset(_target)
endforeach()

foreach(_name
lanczos_scaling
)
set(_test t_${_name})
set(_target test_${_name})

add_executable(${_target} "")
target_sources(${_target} PRIVATE ${_name}.cpp)
target_link_libraries(${_target}
PRIVATE
gil_compile_options
gil_include_directories
gil_dependencies)
add_test(NAME ${_test} COMMAND ${_target})

unset(_name)
unset(_target)
unset(_test)
endforeach()
1 change: 1 addition & 0 deletions test/core/image_processing/Jamfile
Expand Up @@ -15,3 +15,4 @@ project

run binary.cpp ;
run truncate.cpp ;
run lanczos_scaling.cpp ;
65 changes: 65 additions & 0 deletions test/core/image_processing/lanczos_scaling.cpp
@@ -0,0 +1,65 @@
#include <boost/gil/image_processing/scaling.hpp>
#include <boost/gil/image.hpp>

#include <boost/core/lightweight_test.hpp>


#include <iostream>

namespace gil = boost::gil;

bool are_equal(gil::rgb8_view_t expected, gil::rgb8_view_t actual) {
if (expected.dimensions() != actual.dimensions())
return false;

for (long int y = 0; y < expected.height(); ++y)
{
for (long int x = 0; x < expected.width(); ++x)
{
if (expected(x, y) != actual(x, y))
{
return false;
}
}
}

return true;
}

void test_lanczos_black_image()
{

const gil::point_t input_dimensions(20, 20);
const gil::point_t output_dimensions(input_dimensions.x / 2, input_dimensions.y / 2);
gil::rgb8_image_t image(input_dimensions, gil::rgb8_pixel_t(0, 0, 0), 0);
// fill with values other than 0
gil::rgb8_image_t output_image(
output_dimensions,
gil::rgb8_pixel_t(100, 100, 100),
0
);
gil::rgb8_image_t expected(
output_dimensions,
gil::rgb8_pixel_t(0, 0, 0),
0
);

auto view = gil::view(image);
auto output_view = gil::view(output_image);
auto expected_view = gil::view(expected);
gil::scale_lanczos(view, output_view, 5);
BOOST_TEST(are_equal(expected_view,output_view));
}

void test_lanczos_response_on_zero()
{
//random value for a
BOOST_TEST(gil::lanczos(0, 2) == 1);
}

int main()
{
test_lanczos_black_image();
test_lanczos_response_on_zero();
return boost::report_errors();
}

0 comments on commit cf897c5

Please sign in to comment.