Skip to content

Commit

Permalink
refactor: Ellipse rasterizer according to the comment at (#692)
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-langer authored Jun 26, 2022
1 parent bfed3de commit adddbec
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 105 deletions.
5 changes: 3 additions & 2 deletions example/rasterizer_circle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@

#include <boost/gil.hpp>
#include <boost/gil/extension/io/png.hpp>

#include <cmath>
#include <limits>
#include <vector>

namespace gil = boost::gil;

// Demonstrates the use of a rasterizer to generate an image of a circle
// The various rasterizers available are defined in include/boost/gil/rasterization/circle.hpp,
// include/boost/gil/rasterization/ellipse.hpp and include/boost/gil/rasterization/line.hpp
// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp,
// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp
// This example uses a trigonometric rasterizer; GIL also offers the rasterizer midpoint_circle_rasterizer,
// which implements the Midpoint algorithm.
// See also:
Expand Down
30 changes: 15 additions & 15 deletions example/rasterizer_ellipse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// http://www.boost.org/LICENSE_1_0.txt)
//

#include <boost/gil/extension/io/jpeg.hpp>
#include <boost/gil.hpp>
#include <boost/gil/extension/io/jpeg.hpp>

namespace gil = boost::gil;

// Demonstrates the use of a rasterizer to generate an image of an ellipse
// The various rasterizers available are defined in include/boost/gil/rasterization/circle.hpp,
// include/boost/gil/rasterization/ellipse.hpp and include/boost/gil/rasterization/line.hpp
// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp,
// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp
// The rasterizer used is a generalisation of the midpoint algorithm often used for drawing circle.
// This examples also shows how to create images with various pixel depth, as well as the behaviour
// in case of the rasterization of a curve that doesn't fit in a view.
Expand All @@ -25,28 +25,28 @@ namespace gil = boost::gil;
int main()
{
// Syntax for usage :-
// auto rasterizer = gil::midpoint_elliptical_rasterizer{};
// rasterizer(img_view, colour, center, semi-axes_length);
// auto rasterizer = gil::midpoint_ellipse_rasterizer{};
// rasterizer(img_view, pixel, center, semi-axes_length);
// Where
// img_view : gil view of the image on which ellipse is to be drawn.
// colour : Vector containing channel intensity values for img_view. Number of colours
// provided must be equal to the number of channels present in img_view.
// center : Array containing positive integer x co-ordinate and y co-ordinate of the center
// pixel : Pixel value for the elliptical curve to be drawn. Pixel's type must be compatible to the
// pixel type of the image view.
// center : Point containing positive integer x co-ordinate and y co-ordinate of the center
// respectively.
// semi-axes_length : Array containing positive integer lengths of horizontal semi-axis
// semi-axes_length : Point containing positive integer lengths of horizontal semi-axis
// and vertical semi-axis respectively.

gil::gray8_image_t gray_buffer_image(256, 256);
auto gray_elliptical_rasterizer = gil::midpoint_elliptical_rasterizer{};
gray_elliptical_rasterizer(view(gray_buffer_image), {128}, {128, 128}, {100, 50});
auto gray_ellipse_rasterizer = gil::midpoint_ellipse_rasterizer{};
gray_ellipse_rasterizer(view(gray_buffer_image), gil::gray8_pixel_t{128}, {128, 128}, {100, 50});

gil::rgb8_image_t rgb_buffer_image(256, 256);
auto rgb_elliptical_rasterizer = gil::midpoint_elliptical_rasterizer{};
rgb_elliptical_rasterizer(view(rgb_buffer_image), {0, 0, 255}, {128, 128}, {50, 100});
auto rgb_ellipse_rasterizer = gil::midpoint_ellipse_rasterizer{};
rgb_ellipse_rasterizer(view(rgb_buffer_image), gil::rgb8_pixel_t{0, 0, 255}, {128, 128}, {50, 100});

gil::rgb8_image_t rgb_buffer_image_out_of_bound(256, 256);
auto rgb_elliptical_rasterizer_out_of_bound = gil::midpoint_elliptical_rasterizer{};
rgb_elliptical_rasterizer_out_of_bound(view(rgb_buffer_image_out_of_bound), {255, 0, 0},
auto rgb_ellipse_rasterizer_out_of_bound = gil::midpoint_ellipse_rasterizer{};
rgb_ellipse_rasterizer_out_of_bound(view(rgb_buffer_image_out_of_bound), gil::rgb8_pixel_t{255, 0, 0},
{100, 100}, {160, 160});

gil::write_view("rasterized_ellipse_gray.jpg", view(gray_buffer_image), gil::jpeg_tag{});
Expand Down
4 changes: 2 additions & 2 deletions example/rasterizer_line.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
namespace gil = boost::gil;

// Demonstrates the use of a rasterizer to generate an image of a line
// The various rasterizers available are defined in include/boost/gil/rasterization/circle.hpp,
// include/boost/gil/rasterization/ellipse.hpp and include/boost/gil/rasterization/line.hpp
// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp,
// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp
// The rasterizer used implements the Bresenham's line algorithm.
// Multiple images are created, all of the same size, but with areas of different sizes passed to the rasterizer, resulting in different lines.
// See also:
Expand Down
147 changes: 68 additions & 79 deletions include/boost/gil/extension/rasterization/ellipse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
#ifndef BOOST_GIL_EXTENSION_RASTERIZATION_ELLIPSE_HPP
#define BOOST_GIL_EXTENSION_RASTERIZATION_ELLIPSE_HPP

#include <boost/gil/concepts/pixel.hpp>
#include <boost/gil/point.hpp>

#include <array>
#include <stdexcept>
#include <vector>
#include <iostream>

namespace boost { namespace gil {

Expand All @@ -22,21 +25,21 @@ namespace boost { namespace gil {
/// \brief Performs ellipse rasterization using midpoint algorithm. Initially, program considers
/// origin as center of ellipse and obtains first quadrant trajectory points. After that,
/// it shifts origin to provided co-ordinates of center and then draws the curve.
struct midpoint_elliptical_rasterizer
struct midpoint_ellipse_rasterizer
{
/// \brief Returns a vector containing co-ordinates of first quadrant points which lie on
/// rasterizer trajectory of the ellipse.
/// \param semi_axes - Array containing half of lengths of horizontal and vertical axis
/// \param semi_axes - Point containing half of lengths of horizontal and vertical axis
/// respectively.
auto obtain_trajectory(std::array<unsigned int, 2> const semi_axes)
-> std::vector<std::array<std::ptrdiff_t, 2>>
auto obtain_trajectory(point<unsigned int> semi_axes)
-> std::vector<point_t>
{
// Citation : J. Van Aken, "An Efficient Ellipse-Drawing Algorithm" in IEEE Computer
// Graphics and Applications, vol. 4, no. 09, pp. 24-35, 1984.
// doi: 10.1109/MCG.1984.275994
// keywords: {null}
// url: https://doi.ieeecomputersociety.org/10.1109/MCG.1984.275994
std::vector<std::array<std::ptrdiff_t, 2>> trajectory_points;
std::vector<point_t> trajectory_points;
std::ptrdiff_t x = semi_axes[0], y = 0;

// Variables declared on following lines are temporary variables used for improving
Expand Down Expand Up @@ -96,96 +99,82 @@ struct midpoint_elliptical_rasterizer
/// obtained from their reflection along major axis, minor axis and line passing through
/// center with slope -1 using colours provided by user.
/// \param view - Gil view of image on which the elliptical curve is to be drawn.
/// \param colour - Constant vector specifying colour intensity values for all channels present
/// in 'view'.
/// \param center - Constant array specifying co-ordinates of center of ellipse to be drawn.
/// \param pixel - Pixel value for the elliptical curve to be drawn.
/// \param center - Point specifying co-ordinates of center of ellipse to be drawn.
/// \param trajectory_points - Constant vector specifying pixel co-ordinates of points lying
/// on rasterizer trajectory.
/// \tparam View - Type of input image view.
template<typename View>
void draw_curve(View view, std::vector<unsigned int> const colour,
std::array<unsigned int, 2> const center,
std::vector<std::array<std::ptrdiff_t, 2>> const trajectory_points)
/// \tparam Pixel - Type of pixel. Must be compatible to the pixel type of the image view
template<typename View, typename Pixel>
void draw_curve(View& view, Pixel const& pixel,
point<unsigned int> center,
std::vector<point_t> const& trajectory_points)
{
for (int i = 0, colour_index = 0; i < static_cast<int>(view.num_channels());
++i, ++colour_index)
using pixel_t = typename View::value_type;
if (!pixels_are_compatible<pixel_t, Pixel>())
{
for (std::array<std::ptrdiff_t, 2> point : trajectory_points)
throw std::runtime_error("Pixel type of the given image is not compatible to the "
"type of the provided pixel.");
}

--center[0], --center[1]; // For converting center co-ordinate values to zero based indexing.
for (point_t pnt : trajectory_points)
{
std::array<std::ptrdiff_t, 4> co_ords = {center[0] + pnt[0],
center[0] - pnt[0], center[1] + pnt[1], center[1] - pnt[1]
};
bool validity[4]{};
if (co_ords[0] < view.width())
{
validity[0] = true;
}
if (co_ords[1] >= 0 && co_ords[1] < view.width())
{
std::array<std::ptrdiff_t, 4> co_ords = {center[0] + point[0],
center[0] - point[0], center[1] + point[1], center[1] - point[1]
};
bool validity[4] = {0};
if (co_ords[0] < view.width())
{
validity[0] = 1;
}
if (co_ords[1] >= 0 && co_ords[1] < view.width())
{
validity[1] = 1;
}
if (co_ords[2] < view.height())
{
validity[2] = 1;
}
if (co_ords[3] >= 0 && co_ords[3] < view.height())
{
validity[3] = 1;
}
if (validity[0] && validity[2])
{
nth_channel_view(view, i)(co_ords[0], co_ords[2])[0] = colour[colour_index];
}
if (validity[1] && validity[2])
{
nth_channel_view(view, i)(co_ords[1], co_ords[2])[0] = colour[colour_index];
}
if (validity[1] && validity[3])
{
nth_channel_view(view, i)(co_ords[1], co_ords[3])[0] = colour[colour_index];
}
if (validity[0] && validity[3])
{
nth_channel_view(view, i)(co_ords[0], co_ords[3])[0] = colour[colour_index];
}
validity[1] = true;
}
if (co_ords[2] < view.height())
{
validity[2] = true;
}
if (co_ords[3] >= 0 && co_ords[3] < view.height())
{
validity[3] = true;
}

if (validity[0] && validity[2])
{
view(co_ords[0], co_ords[2]) = pixel;
}
if (validity[1] && validity[2])
{
view(co_ords[1], co_ords[2]) = pixel;
}
if (validity[1] && validity[3])
{
view(co_ords[1], co_ords[3]) = pixel;
}
if (validity[0] && validity[3])
{
view(co_ords[0], co_ords[3]) = pixel;
}
}
}

/// \brief Calls the function 'obtain_trajectory' and then passes obtained trajectory points
/// in the function 'draw_curve' for drawing the desired ellipse.
/// \param view - Gil view of image on which the elliptical curve is to be drawn.
/// \param colour - Constant vector specifying colour intensity values for all channels present
/// in 'view'.
/// \param center - Array containing positive integer x co-ordinate and y co-ordinate of the
/// \param pixel - Pixel value for the elliptical curve to be drawn.
/// \param center - Point containing positive integer x co-ordinate and y co-ordinate of the
/// center respectively.
/// \param semi_axes - Array containing positive integer lengths of horizontal semi-axis
/// \param semi_axes - Point containing positive integer lengths of horizontal semi-axis
/// and vertical semi-axis respectively.
/// \tparam View - Type of input image view.
template<typename View>
void operator()(View view, std::vector<unsigned int> const colour,
std::array<unsigned int, 2> center, std::array<unsigned int, 2> const semi_axes)
/// \tparam Pixel - Type of pixel. Must be compatible to the pixel type of the image view
template<typename View, typename Pixel>
void operator()(View& view, Pixel const& pixel,
point<unsigned int> center, point<unsigned int> semi_axes)
{
--center[0], --center[1]; // For converting center co-ordinate values to zero based indexing.
if (colour.size() != view.num_channels())
{
throw std::length_error("Number of channels in given image is not equal to the "
"number of colours provided.");
}
if (center[0] + semi_axes[0] >= view.width() || center[1] + semi_axes[1] >= view.height()
|| static_cast<int>(center[0] - semi_axes[0]) < 0
|| static_cast<int>(center[0] - semi_axes[0]) >= view.width()
|| static_cast<int>(center[1] - semi_axes[1]) < 0
|| static_cast<int>(center[1] - semi_axes[1]) >= view.height())
{
std::cout << "Image can't contain whole curve.\n"
"However, it will contain those parts of curve which can fit inside it.\n"
"Note : Image width = " << view.width() << " and Image height = " <<
view.height() << "\n";
}
std::vector<std::array<std::ptrdiff_t, 2>> trajectory_points =
obtain_trajectory(semi_axes);
draw_curve(view, colour, center, trajectory_points);
draw_curve(view, pixel, center, obtain_trajectory(semi_axes));
}
};

Expand Down
14 changes: 7 additions & 7 deletions test/extension/rasterization/ellipse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/core/lightweight_test.hpp>
#include "boost/gil.hpp"
#include <array>
#include <boost/gil.hpp>

#include <cmath>
#include <vector>

Expand All @@ -17,7 +17,7 @@ namespace gil = boost::gil;
// is equal to the length of major axis of the ellipse.
// Parameters b and a represent half of lengths of vertical and horizontal axis respectively.
void test_rasterizer_follows_equation(
std::vector<std::array<std::ptrdiff_t, 2>> trajectory_points, float a, float b)
std::vector<gil::point_t> const& trajectory_points, float a, float b)
{
float focus_x, focus_y;
if (a > b) // For horizontal ellipse
Expand All @@ -34,7 +34,7 @@ void test_rasterizer_follows_equation(
for (auto trajectory_point : trajectory_points)
{
// To suppress conversion warnings from compiler
std::array<float, 2> point {
gil::point<float> point {
static_cast<float>(trajectory_point[0]), static_cast<float>(trajectory_point[1])};

double dist_sum = std::sqrt(std::pow(focus_x - point[0], 2) +
Expand All @@ -53,7 +53,7 @@ void test_rasterizer_follows_equation(

// This function verifies that the difference between x co-ordinates and y co-ordinates for two
// successive trajectory points is less than or equal to 1. This ensures that the curve is connected.
void test_connectivity(std::vector<std::array<std::ptrdiff_t, 2>> points)
void test_connectivity(std::vector<gil::point_t> const& points)
{
for (std::size_t i = 1; i < points.size(); ++i)
{
Expand All @@ -72,8 +72,8 @@ int main()
{
for (float b = 1; b < 101; ++b)
{
auto rasterizer = gil::midpoint_elliptical_rasterizer{};
std::vector<std::array<std::ptrdiff_t, 2>> points = rasterizer.obtain_trajectory(
auto rasterizer = gil::midpoint_ellipse_rasterizer{};
std::vector<gil::point_t> points = rasterizer.obtain_trajectory(
{static_cast<unsigned int>(a), static_cast<unsigned int>(b)});
test_rasterizer_follows_equation(points, a, b);
test_connectivity(points);
Expand Down

0 comments on commit adddbec

Please sign in to comment.