// Copyright (C) 2008 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_DRAW_IMAGe_
#define DLIB_DRAW_IMAGe_
#include "draw_abstract.h"
#include "../algs.h"
#include "../pixel.h"
#include "../matrix.h"
#include "../gui_widgets/fonts.h"
#include "../geometry.h"
#include <cmath>
namespace dlib
{
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void draw_line (
long x1,
long y1,
long x2,
long y2,
image_type& c_,
const pixel_type& val
)
{
image_view<image_type> c(c_);
if (x1 == x2)
{
// make sure y1 comes before y2
if (y1 > y2)
swap(y1,y2);
if (x1 < 0 || x1 >= c.nc())
return;
// this is a vertical line
for (long y = y1; y <= y2; ++y)
{
if (y < 0 || y >= c.nr())
continue;
assign_pixel(c[y][x1], val);
}
}
else if (y1 == y2)
{
// make sure x1 comes before x2
if (x1 > x2)
swap(x1,x2);
if (y1 < 0 || y1 >= c.nr())
return;
// this is a horizontal line
for (long x = x1; x <= x2; ++x)
{
if (x < 0 || x >= c.nc())
continue;
assign_pixel(c[y1][x] , val);
}
}
else
{
// This part is a little more complicated because we are going to perform alpha
// blending so the diagonal lines look nice.
const rectangle valid_area = get_rect(c);
rgb_alpha_pixel alpha_pixel;
assign_pixel(alpha_pixel, val);
const unsigned char max_alpha = alpha_pixel.alpha;
const long rise = (((long)y2) - ((long)y1));
const long run = (((long)x2) - ((long)x1));
if (std::abs(rise) < std::abs(run))
{
const double slope = ((double)rise)/run;
double first, last;
if (x1 > x2)
{
first = std::max(x2,valid_area.left());
last = std::min(x1,valid_area.right());
}
else
{
first = std::max(x1,valid_area.left());
last = std::min(x2,valid_area.right());
}
long y;
long x;
const double x1f = x1;
const double y1f = y1;
for (double i = first; i <= last; ++i)
{
const double dy = slope*(i-x1f) + y1f;
const double dx = i;
y = static_cast<long>(dy);
x = static_cast<long>(dx);
if (y >= valid_area.top() && y <= valid_area.bottom())
{
alpha_pixel.alpha = static_cast<unsigned char>((1.0-(dy-y))*max_alpha);
assign_pixel(c[y][x], alpha_pixel);
}
if (y+1 >= valid_area.top() && y+1 <= valid_area.bottom())
{
alpha_pixel.alpha = static_cast<unsigned char>((dy-y)*max_alpha);
assign_pixel(c[y+1][x], alpha_pixel);
}
}
}
else
{
const double slope = ((double)run)/rise;
double first, last;
if (y1 > y2)
{
first = std::max(y2,valid_area.top());
last = std::min(y1,valid_area.bottom());
}
else
{
first = std::max(y1,valid_area.top());
last = std::min(y2,valid_area.bottom());
}
long x;
long y;
const double x1f = x1;
const double y1f = y1;
for (double i = first; i <= last; ++i)
{
const double dx = slope*(i-y1f) + x1f;
const double dy = i;
y = static_cast<long>(dy);
x = static_cast<long>(dx);
if (x >= valid_area.left() && x <= valid_area.right())
{
alpha_pixel.alpha = static_cast<unsigned char>((1.0-(dx-x))*max_alpha);
assign_pixel(c[y][x], alpha_pixel);
}
if (x+1 >= valid_area.left() && x+1 <= valid_area.right())
{
alpha_pixel.alpha = static_cast<unsigned char>((dx-x)*max_alpha);
assign_pixel(c[y][x+1], alpha_pixel);
}
}
}
}
}
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void draw_line (
image_type& c,
const point& p1,
const point& p2,
const pixel_type& val
)
{
draw_line(p1.x(),p1.y(),p2.x(),p2.y(),c,val);
}
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void draw_rectangle (
image_type& c,
const rectangle& rect,
const pixel_type& val
)
{
draw_line(c, rect.tl_corner(), rect.tr_corner(), val);
draw_line(c, rect.bl_corner(), rect.br_corner(), val);
draw_line(c, rect.tl_corner(), rect.bl_corner(), val);
draw_line(c, rect.tr_corner(), rect.br_corner(), val);
}
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void draw_rectangle (
image_type& c,
const rectangle& rect,
const pixel_type& val,
unsigned int thickness
)
{
for (unsigned int i = 0; i < thickness; ++i)
{
if ((i%2)==0)
draw_rectangle(c,shrink_rect(rect,(i+1)/2),val);
else
draw_rectangle(c,grow_rect(rect,(i+1)/2),val);
}
}
// ----------------------------------------------------------------------------------------
struct string_dims
{
string_dims() = default;
string_dims (
unsigned long width,
unsigned long height
) : width(width), height(height) {}
unsigned long width = 0;
unsigned long height = 0;
};
template <
typename T, typename traits,
typename alloc
>
string_dims compute_string_dims (
const std::basic_string<T, traits, alloc>& str,
const std::shared_ptr<font>& f_ptr = default_font::get_font()
)
{
using string = std::basic_string<T, traits, alloc>;
const font& f = *f_ptr;
long height = f.height();
long width = 0;
for (typename string::size_type i = 0; i < str.size(); ++i)
{
// ignore the '\r' character
if (str[i] == '\r')
continue;
// A combining character should be applied to the previous character, and we
// therefore make one step back. If a combining comes right after a newline,
// then there must be some kind of error in the string, and we don't combine.
if (is_combining_char(str[i]))
{
width -= f[str[i]].width();
}
if (str[i] == '\n')
{
height += f.height();
width = f.left_overflow();
continue;
}
const letter& l = f[str[i]];
width += l.width();
}
return string_dims(width, height);
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename traits,
typename alloc,
typename image_type,
typename pixel_type
>
void draw_string (
image_type& image,
const dlib::point& p,
const std::basic_string<T,traits,alloc>& str,
const pixel_type& color,
const std::shared_ptr<font>& f_ptr = default_font::get_font(),
typename std::basic_string<T,traits,alloc>::size_type first = 0,
typename std::basic_string<T,traits,alloc>::size_type last = (std::basic_string<T,traits,alloc>::npos)
)
{
using string = std::basic_string<T,traits,alloc>;
DLIB_ASSERT((last == string::npos) || (first <= last && last < str.size()),
"\tvoid dlib::draw_string()"
<< "\n\tlast == string::npos: " << ((last == string::npos)?"true":"false")
<< "\n\tfirst: " << (unsigned long)first
<< "\n\tlast: " << (unsigned long)last
<< "\n\tstr.size(): " << (unsigned long)str.size());
if (last == string::npos)
last = str.size();
const rectangle rect(p, p);
const font& f = *f_ptr;
image_view<image_type> img(image);
long y_offset = rect.top() + f.ascender() - 1;
long pos = rect.left()+f.left_overflow();
convert_to_utf32(str.begin() + first, str.begin() + last, [&](unichar ch)
{
// ignore the '\r' character
if (ch == '\r')
return;
// A combining character should be applied to the previous character, and we
// therefore make one step back. If a combining comes right after a newline,
// then there must be some kind of error in the string, and we don't combine.
if(is_combining_char(ch) &&
pos > rect.left() + static_cast<long>(f.left_overflow()))
{
pos -= f[ch].width();
}
if (ch == '\n')
{
y_offset += f.height();
pos = rect.left()+f.left_overflow();
return;
}
// only look at letters in the intersection area
if (img.nr() + static_cast<long>(f.height()) < y_offset)
{
// the string is now below our rectangle so we are done
return;
}
else if (0 > pos - static_cast<long>(f.left_overflow()) &&
pos + static_cast<long>(f[ch].width() + f.right_overflow()) < 0)
{
pos += f[ch].width();
return;
}
else if (img.nc() + static_cast<long>(f.right_overflow()) < pos)
{
// keep looking because there might be a '\n' in the string that
// will wrap us around and put us back into our rectangle.
return;
}
// at this point in the loop we know that f[str[i]] overlaps
// horizontally with the intersection rectangle area.
const letter& l = f[ch];
for (unsigned short i = 0; i < l.num_of_points(); ++i)
{
const long x = l[i].x + pos;
const long y = l[i].y + y_offset;
// draw each pixel of the letter if it is inside the intersection
// rectangle
if (x >= 0 && x < img.nc() && y >= 0 && y < img.nr())
{
assign_pixel(img[y][x], color);
}
}
pos += l.width();
});
}
template <
typename image_type,
typename pixel_type
>
void draw_string (
image_type& image,
const dlib::point& p,
const char* str,
const pixel_type& color,
const std::shared_ptr<font>& f_ptr = default_font::get_font(),
typename std::string::size_type first = 0,
typename std::string::size_type last = std::string::npos
)
{
draw_string(image, p, std::string(str), color, f_ptr, first, last);
}
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void fill_rect (
image_type& img_,
const rectangle& rect,
const pixel_type& pixel
)
{
image_view<image_type> img(img_);
rectangle area = rect.intersect(get_rect(img));
for (long r = area.top(); r <= area.bottom(); ++r)
{
for (long c = area.left(); c <= area.right(); ++c)
{
assign_pixel(img[r][c], pixel);
}
}
}
// ---------------------------------------------------------------------------------------
template <typename image_type, typename pixel_type>
void draw_solid_convex_polygon (
image_type& image,
const polygon& poly,
const pixel_type& color,
const bool antialias = true,
const rectangle& area = rectangle(std::numeric_limits<long>::min(), std::numeric_limits<long>::min(),
std::numeric_limits<long>::max(), std::numeric_limits<long>::max())
)
{
const rectangle image_rect = get_rect(image);
const rectangle bounding_box = poly.get_rect();
rectangle valid_area(image_rect.intersect(area));
// Don't do anything if the polygon is totally outside the area we can draw in
// right now.
if (bounding_box.intersect(valid_area).is_empty())
return;
image_view<image_type> img(image);
rgb_alpha_pixel alpha_pixel;
assign_pixel(alpha_pixel, color);
const unsigned char max_alpha = alpha_pixel.alpha;
// we will only want to loop over the part of left_boundary that is part of the
// valid_area.
valid_area = shrink_rect(valid_area.intersect(bounding_box), 1);
// Since we look at the adjacent rows of boundary information when doing the alpha
// blending, we want to make sure we always have some boundary information unless
// we are at the absolute edge of the polygon.
const long left_offset = (antialias && valid_area.left() == bounding_box.left()) ? 0 : 1;
const long top_offset = (antialias && valid_area.top() == bounding_box.top()) ? 0 : 1;
const long right_offset = (antialias && valid_area.right() == bounding_box.right()) ? 0 : 1;
const long bottom_offset = (antialias && valid_area.bottom() == bounding_box.bottom()) ? 0 : 1;
if (antialias)
valid_area = grow_rect(valid_area, 1).intersect(image_rect);
std::vector<double> left_boundary;
std::vector<double> right_boundary;
poly.get_left_and_right_bounds(valid_area.top(), valid_area.bottom(), left_boundary, right_boundary);
// draw the polygon row by row
for (unsigned long i = top_offset; i < left_boundary.size(); ++i)
{
long left_x = static_cast<long>(std::ceil(left_boundary[i]));
long right_x = static_cast<long>(std::floor(right_boundary[i]));
left_x = std::max(left_x, valid_area.left());
right_x = std::min(right_x, valid_area.right());
if (i < left_boundary.size() - bottom_offset)
{
// draw the main body of the polygon
for (long x = left_x + left_offset; x <= right_x - right_offset; ++x)
{
const long y = i + valid_area.top();
assign_pixel(img[y][x], color);
}
}
if (i == 0 || !antialias)
continue;
// Now draw anti-aliased edges so they don't look all pixely.
// Alpha blend the edges on the left side.
double delta = left_boundary[i-1] - left_boundary[i];
if (std::abs(delta) <= 1)
{
if (std::floor(left_boundary[i]) != left_x)
{
const point p(static_cast<long>(std::floor(left_boundary[i])), i + valid_area.top());
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = max_alpha-static_cast<unsigned char>((left_boundary[i]-p.x())*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()], temp);
}
}
else if (delta < 0) // on the bottom side
{
for (long x = static_cast<long>(std::ceil(left_boundary[i-1])); x < left_x; ++x)
{
const point p(x, i+valid_area.top());
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = static_cast<unsigned char>((x-left_boundary[i-1])/std::abs(delta)*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()], temp);
}
}
else // on the top side
{
const long old_left_x = static_cast<long>(std::ceil(left_boundary[i-1]));
for (long x = left_x; x < old_left_x; ++x)
{
const point p(x, i + valid_area.top()-1);
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = static_cast<unsigned char>((x-left_boundary[i])/delta*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()],temp);
}
}
// Alpha blend the edges on the right side
delta = right_boundary[i-1] - right_boundary[i];
if (std::abs(delta) <= 1)
{
if (std::ceil(right_boundary[i]) != right_x)
{
const point p(static_cast<long>(std::ceil(right_boundary[i])), i + valid_area.top());
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = max_alpha-static_cast<unsigned char>((p.x()-right_boundary[i])*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()],temp);
}
}
else if (delta < 0) // on the top side
{
for (long x = static_cast<long>(std::floor(right_boundary[i-1]))+1; x <= right_x; ++x)
{
const point p(x, i + valid_area.top() - 1);
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = static_cast<unsigned char>((right_boundary[i]-x)/std::abs(delta)*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()],temp);
}
}
else // on the bottom side
{
const long old_right_x = static_cast<long>(std::floor(right_boundary[i-1]));
for (long x = right_x+1; x <= old_right_x; ++x)
{
const point p(x, i + valid_area.top());
rgb_alpha_pixel temp = alpha_pixel;
temp.alpha = static_cast<unsigned char>((right_boundary[i-1]-x)/delta*max_alpha);
if (valid_area.contains(p))
assign_pixel(img[p.y()][p.x()],temp);
}
}
}
}
// ---------------------------------------------------------------------------------------
template <typename image_type, typename pixel_type>
void draw_solid_polygon (
image_type& image,
const polygon& poly,
const pixel_type& color,
const rectangle& area = rectangle(std::numeric_limits<long>::min(), std::numeric_limits<long>::min(),
std::numeric_limits<long>::max(), std::numeric_limits<long>::max())
)
{
image_view<image_type> img(image);
rectangle valid_area(get_rect(image).intersect(area));
const rectangle bounding_box = poly.get_rect();
if (bounding_box.intersect(valid_area).is_empty())
return;
valid_area = shrink_rect(valid_area.intersect(bounding_box), 1);
std::vector<double> intersections;
for (long y = valid_area.top(); y <= valid_area.bottom(); ++y)
{
// Compute the intersections with the scanline.
intersections.clear();
for (size_t i = 0; i < poly.size(); ++i)
{
const auto& p1 = poly[i];
const auto& p2 = poly[(i + 1) % poly.size()];
// Skip horizontal edges
if (p1.y() == p2.y())
continue;
// Skip if there are no intersections at this position
if ((y <= p1.y() && y <= p2.y()) || (y > p1.y() && y > p2.y()))
continue;
// Add x coordinates of intersecting points
intersections.push_back(p1.x() + (y - p1.y()) * (p2.x() - p1.x()) / static_cast<double>(p2.y() - p1.y()));
}
std::sort(intersections.begin(), intersections.end());
for (size_t i = 0; i < intersections.size(); i += 2)
{
long left_x = static_cast<long>(std::ceil(intersections[i]));
long right_x = static_cast<long>(std::floor(intersections[i + 1]));
left_x = std::max(left_x, valid_area.left());
right_x = std::min(right_x, valid_area.right());
// Draw the main body of the polygon
for (long x = left_x; x <= right_x; ++x)
{
assign_pixel(img[y][x], color);
}
}
}
}
// ---------------------------------------------------------------------------------------
template <
typename image_array_type
>
matrix<typename image_traits<typename image_array_type::value_type>::pixel_type> tile_images (
const image_array_type& images
)
{
typedef typename image_traits<typename image_array_type::value_type>::pixel_type T;
if (images.size() == 0)
return matrix<T>();
const unsigned long size_nc = square_root(images.size());
const unsigned long size_nr = (size_nc*(size_nc-1)>=images.size())? size_nc-1 : size_nc;
// Figure out the size we have to use for each chip in the big main image. We will
// use the largest dimensions seen across all the chips.
long nr = 0;
long nc = 0;
for (unsigned long i = 0; i < images.size(); ++i)
{
nr = std::max(num_rows(images[i]), nr);
nc = std::max(num_columns(images[i]), nc);
}
matrix<T> temp(size_nr*nr, size_nc*nc);
T background_color;
assign_pixel(background_color, 0);
temp = background_color;
unsigned long idx = 0;
for (unsigned long r = 0; r < size_nr; ++r)
{
for (unsigned long c = 0; c < size_nc; ++c)
{
if (idx < images.size())
{
set_subm(temp, r*nr, c*nc, nr, nc) = mat(images[idx]);
}
++idx;
}
}
return temp;
}
// ----------------------------------------------------------------------------------------
template <
typename image_type,
typename pixel_type
>
void draw_solid_circle (
image_type& img_,
const dpoint& center_point,
double radius,
const pixel_type& pixel
)
{
image_view<image_type> img(img_);
using std::sqrt;
const rectangle valid_area(get_rect(img));
const double x = center_point.x();
const double y = center_point.y();
const point cp(center_point);
if (radius > 1)
{
long first_x = static_cast<long>(x - radius + 0.5);
long last_x = static_cast<long>(x + radius + 0.5);
const double rs = radius*radius;
// ensure that we only loop over the part of the x dimension that this
// image contains.
if (first_x < valid_area.left())
first_x = valid_area.left();
if (last_x > valid_area.right())
last_x = valid_area.right();
long top, bottom;
top = std::lround(sqrt(std::max(rs - (first_x-x-0.5)*(first_x-x-0.5),0.0)));
top += y;
long last = top;
// draw the left half of the circle
long middle = std::min(cp.x()-1,last_x);
for (long i = first_x; i <= middle; ++i)
{
double a = i - x + 0.5;
// find the top of the arc
top = std::lround(sqrt(std::max(rs - a*a,0.0)));
top += y;
long temp = top;
while(top >= last)
{
bottom = y - top + y;
draw_line(img_, point(i,top),point(i,bottom),pixel);
--top;
}
last = temp;
}
middle = std::max(cp.x(),first_x);
top = std::lround(sqrt(std::max(rs - (last_x-x+0.5)*(last_x-x+0.5),0.0)));
top += y;
last = top;
// draw the right half of the circle
for (long i = last_x; i >= middle; --i)
{
double a = i - x - 0.5;
// find the top of the arc
top = std::lround(sqrt(std::max(rs - a*a,0.0)));
top += y;
long temp = top;
while(top >= last)
{
bottom = y - top + y;
draw_line(img_, point(i,top),point(i,bottom),pixel);
--top;
}
last = temp;
}
}
else if (valid_area.contains(cp))
{
// For circles smaller than a pixel we will just alpha blend them in proportion
// to how small they are.
rgb_alpha_pixel temp;
assign_pixel(temp, pixel);
temp.alpha = static_cast<unsigned char>(255*radius + 0.5);
assign_pixel(img[cp.y()][cp.x()], temp);
}
}
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_DRAW_IMAGe_