Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) DrawCanvas

 

STLQt CreatorLubuntuWindows

 

DrawCanvas is a Canvas to draw on.

Technical facts

 

 

 

 

 

 

./CppDrawCanvas/CppDrawCanvas.pri

 

INCLUDEPATH += \
    ../../Classes/CppDrawCanvas

SOURCES += \
    ../../Classes/CppDrawCanvas/drawcanvas.cpp

HEADERS  += \
    ../../Classes/CppDrawCanvas/drawcanvas.h

OTHER_FILES += \
    ../../Classes/CppDrawCanvas/Licence.txt

 

 

 

 

 

./CppDrawCanvas/drawcanvas.h

 

//---------------------------------------------------------------------------
/*
DrawCanvas, ASCII art painting surface class
Copyright (C) 2008-2015 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
// From http://www.richelbilderbeek.nl/CppDrawCanvas.htm
//---------------------------------------------------------------------------
#ifndef RIBI_DRAWCANVAS_H
#define RIBI_DRAWCANVAS_H

#include <iosfwd>
#include <string>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/signals2.hpp>
#include <boost/units/base_units/angle/radian.hpp>
#include <boost/units/systems/si/io.hpp>
#include "canvas.h"
#include "canvascolorsystem.h"
#include "canvascoordinatsystem.h"
#pragma GCC diagnostic pop

struct QRegExp;

namespace ribi {

///The DrawCanvas is an ASCII art tool to draw on
///The DrawCanvas has a coordinat system of (0,0)-(width,height)
///similar to the possible character position on a screen.
///Everything drawn beyond the range of DrawCanvas is not stored.
///Yet, if for example a line is drawn between two off-screen coordinats,
///the part that goes through the DrawCanvas is drawn and stored
struct DrawCanvas : public Canvas
{
  typedef boost::geometry::model::d2::point_xy<double> Coordinat;

  ///The number of characters the DrawCanvas is heigh and wide
  ///but also the maximum x and y coordinat. The minimum
  ///x and y coordinats are 0.0 and 0.0
  DrawCanvas(
    const int width  = 1,
    const int height = 1,
    const CanvasColorSystem color_system = CanvasColorSystem::normal,
    const CanvasCoordinatSystem coordinat_system = CanvasCoordinatSystem::screen);

  ///Create a DrawCanvas from its raw internals
  DrawCanvas(
    const std::vector<std::vector<double>>& canvas,
    const CanvasColorSystem color_system = CanvasColorSystem::normal,
    const CanvasCoordinatSystem coordinat_system = CanvasCoordinatSystem::screen);

  ///Load a DrawCanvas from file
  DrawCanvas(const std::string& filename);

  ~DrawCanvas() noexcept {}

  ///Clears the canvas
  void Clear() noexcept;

  //Draws the arc defined by the given rectangle, startAngle and spanAngle.
  //Positive values for the angles mean clockwise,
  //while negative values mean the clockwise direction.
  //Zero degrees is at the 12 o'clock position.
  void DrawArc(const double left, const double top, const double right, const double bottom,
    const boost::units::quantity<boost::units::si::plane_angle> startAngle,
    const boost::units::quantity<boost::units::si::plane_angle> spanAngle
  ) noexcept;

  ///Draw (or actually: add) a circle on the canvas at (xMid,yMid),
  ///with radius ray
  void DrawCircle(const double xMid, const double yMid, const double ray) noexcept;

  ///Draw (or actually: add) a dot on the canvas at (x,y), where
  ///(x,y) is the center of a dot with radius 1.0. It is not checked that
  ///(x,y) is in ( [0.0,GetWidth()>, [0.0,GetHeight()> )
  void DrawDot(const double x, const double y) noexcept;

  ///Draw (or actually: add) an ellipse on the canvas
  void DrawEllipse(const double left, const double top, const double right, const double bottom) noexcept;

  ///Draw (or actually: add) a line on the canvas from (x1,y1) to (x2,y2),
  ///where (x1,y1) and (x2,y2) are the centers of a dot with radius 1.0 at
  ///the edges of the line
  void DrawLine(const double x1, const double y1, const double x2, const double y2) noexcept;
  void DrawLine(const Coordinat from, const Coordinat to) noexcept;

  ///Draw (or actually: add) a polygon on the canvas
  void DrawPolygon(const boost::geometry::model::polygon<Coordinat>& polygon) noexcept;

  ///Draw a Y-X-ordered surface to the DrawCanvas
  void DrawSurface(const std::vector<std::vector<double>>& v);

  ///Draw (or actually: add) text to the DrawCanvas, where (top,left) is the topleft coordinat
  ///of the text. The text will end up as dots drawn for each character its pixel.
  ///The DotMatrix font is used, with a spacing of two pixel, as the letters tend to
  ///overlap otherwise.
  //
  //  DrawCanvas::DrawText(1,1,"Hello world") results in:
  //
  //  M   M          MM     MM                                        MM        M
  //  M   M           M      M                                         M        M
  //  M   M   MMM     M      M     MMM          M   M   MMM   M MM     M     MM M
  //  MMMMM  M   M    M      M    M   M         M   M  M   M  MM  M    M    M  MM
  //  M   M  MMMMM    M      M    M   M         M M M  M   M  M        M    M   M
  //  M   M  M        M      M    M   M         M M M  M   M  M        M    M   M
  //  M   M   MMM    MMM    MMM    MMM           M M    MMM   M       MMM    MMMM
  //
  void DrawText(const double top, const double left, const std::string& text) noexcept;

  ///The color system used:
  ///- normal: full/drawn is displayed by M
  ///- invert: empty/non-drawn is displayed by M
  CanvasColorSystem GetColorSystem() const noexcept { return m_color_system; }

  ///The coordinat system used in displayal:
  ///- screen: origin is at top-left of the screen
  ///- graph: origin is at bottom-left of the screen
  CanvasCoordinatSystem GetCoordinatSystem() const noexcept { return m_coordinat_system; }

  ///The DrawCanvas its internal data: a 2D y-x-ordered std::vector
  ///of doubles, where 0.0 denotes empty/non-drawn
  ///and 1.0 denotes full/drawn.
  const std::vector<std::vector<double>>& GetGreynesses() const noexcept { return m_canvas; }

  ///Obtain the height of the canvas is characters
  int GetHeight() const noexcept override { return m_canvas.size(); }

  ///Obtain the version of this class
  static std::string GetVersion() noexcept;

  ///Obtain the version history of this class
  static std::vector<std::string> GetVersionHistory() noexcept;

  ///Obtain the width of the canvas is characters
  int GetWidth() const noexcept override { return (GetHeight()==0 ? 0 : m_canvas[0].size() ); }

  ///Save to file
  void Save(const std::string& filename) const noexcept;

  ///Set the color system used
  void SetColorSystem(const CanvasColorSystem color_system) noexcept;

  ///Set the coordinat system used
  void SetCoordinatSystem(const CanvasCoordinatSystem coordinat_system) noexcept;

  std::vector<std::string> ToStrings() const noexcept override;

  private:
  ///The DrawCanvas its internal data: a 2D y-x-ordered std::vector
  ///of doubles, where 0.0 denotes empty/non-drawn
  ///and 1.0 denotes full/drawn.
  std::vector<std::vector<double>> m_canvas;

  ///The color system used:
  ///- normal: full/drawn is displayed by M
  ///- invert: empty/non-drawn is displayed by M
  CanvasColorSystem m_color_system;

  ///The coordinat system used in displayal:
  ///- screen: origin is at top-left of the screen
  ///- graph: origin is at bottom-left of the screen
  CanvasCoordinatSystem m_coordinat_system;

  ///From http://www.richelbilderbeek.nl/CppGetRegexMatches.htm
  //static std::vector<std::string> GetRegexMatches(
  //  const std::string& s,
  //  const QRegExp& r);

  ///Check if a coordinat is in the range of the DrawCanvas
  bool IsInRange(const int x, const int y) const;

  //Obtains the minimum element of a 2D container
  //From http://www.richelbilderbeek.nl/CppMinElement.htm
  template <class Container>
  static const typename Container::value_type::value_type MinElement(const Container& v);

  //Obtains the maximal element of a 2D container
  //From http://www.richelbilderbeek.nl/CppMaxElement.htm
  template <class Container>
  static const typename Container::value_type::value_type MaxElement(const Container& v);

  ///Plot a surface on screen
  ///if as_screen_coordinat_system is true, the origin is in the top left
  ///corner of the screen, else it is in the bottom left of the screen,
  ///as is usual in graphs
  //From http://www.richelbilderbeek.nl/CppPlotSurface.htm
  static void PlotSurface(
    std::ostream& os,
    const std::vector<std::vector<double>>& v,
    const bool use_normal_color_system,
    const bool as_screen_coordinat_system);


  #ifndef NDEBUG
  static void Test() noexcept;
  #endif

  friend std::ostream& operator<<(std::ostream& os, const DrawCanvas& canvas);
  friend bool operator==(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept;

};

std::ostream& operator<<(std::ostream& os, const DrawCanvas& canvas);

///Useful for exact comparison
bool operator==(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept;
bool operator!=(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept;

///Useful for fuzzy comparison, like (1) Save (2) Load (3) Compare original
///with loaded version
bool IsAboutEqual(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept;

} //~namespace ribi

#endif // RIBI_DRAWCANVAS_H

 

 

 

 

 

./CppDrawCanvas/drawcanvas.cpp

 

//---------------------------------------------------------------------------
/*
DrawCanvas, ASCII art painting surface class
Copyright (C) 2008-2015 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
// From http://www.richelbilderbeek.nl/CppDrawCanvas.htm
//---------------------------------------------------------------------------
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include "drawcanvas.h"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>

#include <boost/algorithm/string/split.hpp>
#include <boost/geometry.hpp>
#include <boost/math/constants/constants.hpp>

#include <QString>

#include "canvascolorsystems.h"
#include "canvascoordinatsystems.h"
#include "container.h"
#include "dotmatrixstring.h"
#include "fileio.h"
#include "geometry.h"
#include "ribi_regex.h"
#include "testtimer.h"
#include "trace.h"
#include "xml.h"

#pragma GCC diagnostic pop

ribi::DrawCanvas::DrawCanvas(
  const int width,
  const int height,
  const CanvasColorSystem color_system,
  const CanvasCoordinatSystem coordinat_system)
  :
    m_canvas(std::vector<std::vector<double>>(height,std::vector<double>(width,0.0))),
    m_color_system(color_system),
    m_coordinat_system(coordinat_system)
{
  #ifndef NDEBUG
  Test();
  #endif
  assert(width  > 0);
  assert(height > 0);
}

ribi::DrawCanvas::DrawCanvas(
  const std::vector<std::vector<double>>& canvas,
  const CanvasColorSystem color_system,
  const CanvasCoordinatSystem coordinat_system)
  : m_canvas(canvas),
    m_color_system(color_system),
    m_coordinat_system(coordinat_system)
{
  #ifndef NDEBUG
  Test();
  #endif
  assert(!canvas.empty());
  assert(!canvas[0].empty());
}

ribi::DrawCanvas::DrawCanvas(const std::string& filename)
  : m_canvas{},
    m_color_system{},
    m_coordinat_system{}
{
  assert(fileio::FileIo().IsRegularFile(filename));
  std::string s;
  {
    std::ifstream f(filename.c_str());
    f >> s;
  }
  assert(s.size() >= 17);
  assert(s.substr(0,8) == "<canvas>");
  assert(s.substr(s.size() - 9,9) == "</canvas>");
  {
    const std::vector<std::string> v { Regex().GetRegexMatches(s,"(<color_system>.*</color_system>)") };
    assert(v.size() == 1);
    m_color_system = CanvasColorSystems().ToType(ribi::xml::StripXmlTag(v[0]));
  }

  {
    const std::vector<std::string> v { Regex().GetRegexMatches(s,"(<coordinat_system>.*</coordinat_system>)") };
    assert(v.size() == 1);
    m_coordinat_system = CanvasCoordinatSystems().ToType(ribi::xml::StripXmlTag(v[0]));
  }
  int n_cols = -1;
  {
    const std::vector<std::string> v { Regex().GetRegexMatches(s,"(<n_cols>.*</n_cols>)") };
    assert(v.size() == 1);
    //assert(CanCast<int>(ribi::xml::StripXmlTag(v[0])));
    n_cols = boost::lexical_cast<int>(ribi::xml::StripXmlTag(v[0]));
  }

  m_canvas.push_back( {} );
  {
    const std::vector<std::string> v { Regex().GetRegexMatches(s,"(<data>.*</data>)") };
    assert(v.size() == 1 && "(<data>.*</data>) must be present exactly once");
    const std::pair<std::string,std::vector<std::string>> lines { xml::XmlToVector(v[0]) };
    assert(lines.first == "data");
    const std::vector<std::string>& data { lines.second };
    int i = 0;
    for (const std::string& s: data)
    {
      const double d = boost::lexical_cast<double>(s);

      m_canvas.back().push_back(d);
      ++i;
      if (i == n_cols)
      {
        m_canvas.push_back( {} );
        i = 0;
      }
    }
  }
  assert(m_canvas.back().empty());
  m_canvas.pop_back();
}

void ribi::DrawCanvas::Clear() noexcept
{
  for (auto& row: m_canvas)
  {
    for (auto& cell:row)
    {
      cell = 0.0;
    }
  }

  #ifndef NDEBUG
  for (const auto& row: m_canvas)
  {
    assert(std::accumulate(row.begin(),row.end(),0.0) == 0.0);
  }
  #endif
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawArc(
  const double left, const double top, const double right, const double bottom,
    const boost::units::quantity<boost::units::si::plane_angle> startAngle,
    const boost::units::quantity<boost::units::si::plane_angle> spanAngle) noexcept
{
  assert(left < right);
  assert(top < bottom);
  const double midx = (left + right) / 2.0;
  const double midy = (top + bottom) / 2.0;
  const double pi = boost::math::constants::pi<double>();
  const double ray_horizontal = (right  - left) / 2.0;
  const double ray_vertical   = (bottom - top ) / 2.0;
  const double average_ray    = (ray_horizontal + ray_vertical) / 2.0;
  const double arclength = average_ray * pi * 2.0 * (spanAngle.value() / (2.0 * pi));
  const int n_steps = std::abs(static_cast<int>(arclength + 0.5));
  if (n_steps == 0) return;
  assert(n_steps > 0);
  double angle { startAngle.value() };
  const double dAngle = spanAngle.value() / static_cast<double>(n_steps);
  for (int i=0; i!=n_steps; ++i)
  {
    double x = midx + (std::sin(angle) * ray_horizontal);
    double y = midy - (std::cos(angle) * ray_vertical);
    DrawDot(x,y);
    angle += dAngle;
  }
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawCircle(const double xMid, const double yMid, const double ray) noexcept
{
  const double pi = boost::math::constants::pi<double>();
  const double circumference = ray * pi * 2.0;
  const int n_steps = static_cast<int>(circumference + 0.5);
  if (n_steps == 0) return;
  assert(n_steps > 0);
  const double dAngle = 2.0 * pi / static_cast<double>(n_steps);
  double angle = 0.0;
  for (int i=0; i!=n_steps; ++i)
  {
    double x = xMid + (std::sin(angle) * ray);
    double y = yMid - (std::cos(angle) * ray);
    DrawDot(x,y);
    angle += dAngle;
  }
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawDot(const double x, const double y) noexcept
{
  //Assume a dot has dimensions 1.0 x 1.0
  //and x and y are exactly in the middle of this dot
  const double xBegin = x - 0.5;
  const double yBegin = y - 0.5;
  const double fracLeft = std::ceil(xBegin) - xBegin;
  const double fracTop  = std::ceil(yBegin) - yBegin;
  const int indexLeft = std::floor(xBegin);
  const int indexTop  = std::floor(yBegin);
  if (IsInRange(indexLeft  ,indexTop  ))
    m_canvas[indexTop  ][indexLeft  ] += (fracLeft * fracTop);
  if (IsInRange(indexLeft+1,indexTop  ))
    m_canvas[indexTop  ][indexLeft+1] += ((1.0-fracLeft) * fracTop);
  if (IsInRange(indexLeft  ,indexTop+1))
    m_canvas[indexTop+1][indexLeft  ] += (fracLeft * (1.0-fracTop));
  if (IsInRange(indexLeft+1,indexTop+1))
    m_canvas[indexTop+1][indexLeft+1] += ((1.0-fracLeft) * (1.0-fracTop));
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawEllipse(const double left, const double top, const double right, const double bottom) noexcept
{
  assert(left < right);
  assert(top < bottom);
  const double midx = (left + right) / 2.0;
  const double midy = (top + bottom) / 2.0;
  assert(midx > 0.0);
  const double pi = boost::math::constants::pi<double>();
  const double ray_horizontal = (right  - left) / 2.0;
  const double ray_vertical   = (bottom - top ) / 2.0;
  assert(ray_horizontal > 0.0);
  assert(ray_vertical > 0.0);
  const double average_ray    = (ray_horizontal + ray_vertical) / 2.0;
  assert(average_ray > 0.0);
  const double circumference = average_ray * pi * 2.0;
  const int n_steps = static_cast<int>(circumference + 0.5);
  assert(n_steps > 0);
  const double d_angle = 2.0 * pi / static_cast<double>(n_steps);
  assert(d_angle > 0.0);
  double angle = 0.0;
  for (int i=0; i!=n_steps; ++i)
  {
    const double x = midx + (std::sin(angle) * ray_horizontal);
    const double y = midy - (std::cos(angle) * ray_vertical);
    DrawDot(x,y);
    angle += d_angle;
  }
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawSurface(const std::vector<std::vector<double>>& v)
{
  if (m_canvas != v)
  {
    m_canvas = v;
    m_signal_changed(this);
  }
}

void ribi::DrawCanvas::DrawLine(const double x1, const double y1, const double x2, const double y2) noexcept
{

  const double dx = x2 - x1;
  const double dy = y2 - y1;
  const double dist = Geometry().GetDistance(dx,dy);
  const double step_x = dx / dist;
  const double step_y = dy / dist;
  const int n_steps = static_cast<int>(dist + 0.5);
  double x = x1;
  double y = y1;
  for (int i=0; i!=n_steps; ++i)
  {
    DrawDot(x,y);
    x+=step_x;
    y+=step_y;
  }
  m_signal_changed(this);
}

void ribi::DrawCanvas::DrawLine(
  const ribi::DrawCanvas::Coordinat from,
  const ribi::DrawCanvas::Coordinat to
) noexcept
{
  DrawLine(from.x(),from.y(),to.x(),to.y());
}

void ribi::DrawCanvas::DrawPolygon(
  const boost::geometry::model::polygon<ribi::DrawCanvas::Coordinat>& polygon
) noexcept
{
  const std::vector<Coordinat> points = polygon.outer();
  const int n = static_cast<int>(points.size());
  for (int i=0; i!=n; ++i)
  {
    DrawLine(points[i],points[ (i + 1) % n]);
  }
}

void ribi::DrawCanvas::DrawText(const double top, const double left, const std::string& text) noexcept
{
  const int spacing = 2;
  const boost::shared_ptr<const ribi::DotMatrixString> m {
    new ribi::DotMatrixString(text,spacing)
  };
  const int width  = m->GetMatrixWidth();
  const int height = m->GetMatrixHeight();
  for (int y=0; y!=height; ++y)
  {
    for (int x=0; x!=width; ++x)
    {
      if (m->GetMatrix(x,y))
      {
        DrawDot(
          left + static_cast<double>(x) + 0.5,
          top  + static_cast<double>(y) + 0.5
        );
      }
    }
  }
}

std::string ribi::DrawCanvas::GetVersion() noexcept
{
  return "3.1";
}

std::vector<std::string> ribi::DrawCanvas::GetVersionHistory() noexcept
{
  return {
    "2008-xx-xx: version 1.0: initial C++ Builder version, initially called Canvas",
    "2013-08-21: version 2.0: port to C++11 under Qt Creator",
    "2013-08-22: version 2.1: allow two color and coordinat systems"
    "2014-01-07: version 2.2: added the DrawText member function",
    "2014-01-10: version 3.0: renamed to DrawCanvas, inherits from new class called Canvas",
    "2014-05-10: version 3.1: allow to draw a Boost.Geometry polygon, increase support for Boost.Geometry"
  };
}

bool ribi::DrawCanvas::IsInRange(const int x, const int y) const
{
  if (   x < 0
      || y < 0
      || y >= static_cast<int>(m_canvas.size())
      || x >= static_cast<int>(m_canvas[y].size())
     )
    return false;
  return true;
}

void ribi::DrawCanvas::PlotSurface(
  std::ostream& os,
  const std::vector<std::vector<double>>& v,
  const bool use_normal_color_system,
  const bool as_screen_coordinat_system)
{
  assert(v.empty() == false && "Surface must have a size");
  assert(v[0].size() > 0 && "Surface must have a two-dimensional size");

  //Obtain the ASCII art gradient and its size
  static const std::vector<char> asciiArtGradient = GetAsciiArtGradient();
  static const int nAsciiArtGradientChars = asciiArtGradient.size();

  //Minimum and maximum are not given, so these need to be calculated
  const double minVal = MinElement(v);
  double maxVal = MaxElement(v);
  if (minVal == maxVal)
  {
    maxVal = minVal == 0.0 ? 1.0 : minVal * 2.0;
  }

  //Draw the pixels

  const auto row_function(
    [](const std::vector<double>& row,
      std::ostream& os,
      const double minVal,
      const double maxVal,
      const bool use_normal_color_system)
    {
      //Iterate through each row's columns
      const std::vector<double>::const_iterator colEnd = row.end();
      for (std::vector<double>::const_iterator col = row.begin();
        col != colEnd;
        ++col)
      {
        //Scale the found grey value to an ASCII art character
        assert(maxVal != minVal);
        assert(maxVal - minVal != 0.0);
        assert(maxVal > minVal);
        const double greyValueDouble = ( (*col) - minVal) / (maxVal - minVal);
        assert(greyValueDouble >= 0.0 && greyValueDouble <= 1.0);
        const int greyValueInt
          = (use_normal_color_system
          ? greyValueDouble
          : 1.0 - greyValueDouble
          ) * nAsciiArtGradientChars;
        const int greyValue
          = ( greyValueInt < 0
          ? 0 : (greyValueInt > nAsciiArtGradientChars - 1
            ? nAsciiArtGradientChars - 1: greyValueInt) );
        assert(greyValue >= 0 && greyValue < nAsciiArtGradientChars);
        os << asciiArtGradient[greyValue];
      }
      os << std::endl;

    }
  );

  //Iterator through all rows
  if (as_screen_coordinat_system)
  {
    for (const auto& row: v)
    {
      row_function(row,os,minVal,maxVal,use_normal_color_system);
    }
  }
  else
  {
    const auto rowEnd = v.rend();
    for (auto row = v.rbegin(); row != rowEnd; ++row)
    {
      row_function(*row,os,minVal,maxVal,use_normal_color_system);
    }
  }
}

template <class Container>
const typename Container::value_type::value_type ribi::DrawCanvas::MinElement(const Container& v)
{
  assert(v.empty() == false && "Container must have a size");
  //Obtain an initial lowest value
  typename Container::value_type::value_type minValue
    = *(std::min_element(v[0].begin(),v[0].end()));

  //Set the iterators
  const typename Container::const_iterator rowEnd = v.end();
  typename Container::const_iterator row = v.begin();
  ++row; //Move to the next position, as index 0 is already read from

  for ( ; row != rowEnd; ++row) //row is already initialized
  {
    const typename Container::value_type::value_type localMinVal
      = *(std::min_element(row->begin(),row->end()));
    if (localMinVal < minValue) minValue = localMinVal;
  }
  return minValue;
}

template <class Container>
const typename Container::value_type::value_type ribi::DrawCanvas::MaxElement(const Container& v)
{
  assert(v.empty() == false && "Container must have a size");

  //Obtain an initial heighest value
  typename Container::value_type::value_type maxValue
    = *(std::max_element(v[0].begin(),v[0].end()));

  //Set the iterators
  const typename Container::const_iterator rowEnd = v.end();
  typename Container::const_iterator row = v.begin();
  ++row; //Move to the next position, as index 0 is already read from
  for ( ; row != rowEnd; ++row) //row is already initialized
  {
    const typename Container::value_type::value_type localMaxVal
      = *(std::max_element(row->begin(),row->end()));
    if (localMaxVal > maxValue) maxValue = localMaxVal;
  }
  return maxValue;
}

void ribi::DrawCanvas::Save(const std::string& filename) const noexcept
{
  std::stringstream s;
  {
    std::vector<std::string> v;
    for (const auto& line: m_canvas)
    {
      for (const auto& element: line)
      {
        const std::string t { boost::lexical_cast<std::string>(element) };
        v.push_back(t);
      }
    }
    s << xml::VectorToXml("data",v);
    s << xml::ToXml("n_cols",GetWidth());
    //color system
    s << xml::ToXml("color_system",CanvasColorSystems().ToStr(m_color_system));
    //coordinat system
    s << xml::ToXml("coordinat_system",CanvasCoordinatSystems().ToStr(m_coordinat_system));
  }
  {
    const std::string t = xml::ToXml("canvas",s.str());
    std::ofstream f(filename.c_str());
    f << t;
  }
  #ifndef NDEBUG
  {
    DrawCanvas c(filename);
    if (!IsAboutEqual(*this,c))
    {
      TRACE("ERROR");
      TRACE(*this);
      TRACE(c);
    }
    assert(IsAboutEqual(*this,c));
  }
  #endif
}

void ribi::DrawCanvas::SetColorSystem(const CanvasColorSystem colorSystem) noexcept
{
  if (this->m_color_system != colorSystem)
  {
    this->m_color_system = colorSystem;
    this->m_signal_changed(this);
  }
}

void ribi::DrawCanvas::SetCoordinatSystem(const CanvasCoordinatSystem coordinatSystem) noexcept
{
  if (this->m_coordinat_system != coordinatSystem)
  {
    this->m_coordinat_system = coordinatSystem;
    this->m_signal_changed(this);
  }
}

#ifndef NDEBUG
void ribi::DrawCanvas::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  {
    Container();
    DotMatrixString("X",1);
    CanvasColorSystems();
    CanvasCoordinatSystems();
    fileio::FileIo();
    Geometry();
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);
  //Drawing text
  {
    const int maxx = 90;
    const int maxy = 18;
    const boost::shared_ptr<DrawCanvas> canvas(new DrawCanvas(maxx,maxy,CanvasColorSystem::invert));
    std::stringstream s_before;
    s_before << (*canvas);
    const std::string str_before {s_before.str() };
    assert(static_cast<int>(str_before.size()) - maxy == maxx * maxy); //-maxy, as newlines are added
    assert(std::count(str_before.begin(),str_before.end(),' ') == maxx * maxy); //Only spaces

    canvas->DrawText(1,1,"Hello world");

    std::stringstream s_after;
    s_after << (*canvas);
    const std::string str_after {s_after.str() };
    assert(std::count(str_after.begin(),str_after.end(),' ') != maxx * maxy); //Line trly drawn
  }
  //Is a line that starts and ends beyond the canvas drawn?
  {
    const int maxx = 3;
    const int maxy = 4;
    const boost::shared_ptr<DrawCanvas> canvas(new DrawCanvas(maxx,maxy,CanvasColorSystem::invert));
    std::stringstream s_before;
    s_before << (*canvas);
    const std::string str_before {s_before.str() };
    assert(static_cast<int>(str_before.size()) - maxy == maxx * maxy); //-maxy, as newlines are added
    assert(std::count(str_before.begin(),str_before.end(),' ') == maxx * maxy); //Only spaces

    canvas->DrawLine(-maxx,-maxy,maxx*2.0,maxy*2.0);

    std::stringstream s_after;
    s_after << (*canvas);
    const std::string str_after {s_after.str() };
    assert(std::count(str_after.begin(),str_after.end(),' ') != maxx * maxy); //Line trly drawn
  }
  //Draw a polygon
  {
    /*

    6 +
      |
    5 +      -C
      |     - |
    4 +   --  |
      |  -    |
    3 + B     |
      | |    |
    2 + |    |
      | |   |
    1 + A---D
      |
    0 +-+-+-+-+-+-+

      0 1 2 3 4 5 6
    */
    const int maxx = 22;
    const int maxy = 22;
    const boost::shared_ptr<DrawCanvas> canvas(
      new DrawCanvas(
        maxx,
        maxy,
        CanvasColorSystem::invert,
        CanvasCoordinatSystem::graph
      )
    );
    const std::vector<Coordinat> points {
      {  4.0,  4.0}, //A
      {  4.0, 12.0}, //B
      { 16.0, 20.0}, //C
      { 12.0,  4.0}  //D
    };
    boost::geometry::model::polygon<Coordinat> polygon;
    boost::geometry::append(polygon,points);
    canvas->DrawPolygon(polygon);
    {
      std::stringstream s;
      s << (*canvas);
      assert(!s.str().empty());
    }
  }
  //Draw a smiley is all coordinat- and colorsystem combinations
  for (int i=0; i!=4; ++i)
  {
    const int maxx = 79;
    const int maxy = 23;
    const boost::shared_ptr<DrawCanvas> canvas(new DrawCanvas(maxx,maxy));
    canvas->SetColorSystem(
      i % 2
      ? CanvasColorSystem::normal
      : CanvasColorSystem::invert);
    canvas->SetCoordinatSystem(
      i / 2
      ? CanvasCoordinatSystem::screen
      : CanvasCoordinatSystem::graph);

    //Determine and calculate dimensions and coordinats of smiley
    const double maxxD = static_cast<double>(maxx);
    const double maxyD = static_cast<double>(maxy);
    const double midX        = 0.50 * maxxD;
    const double midY        = 0.50 * maxyD;
    const double headRay     = 0.50 * maxyD;
    const double eyeLeftX    = 0.50 * maxxD - (0.35 * headRay) ;
    const double eyeLeftY    = 0.50 * maxyD - (0.25 * headRay) ;
    const double eyeRightX   = 0.50 * maxxD + (0.35 * headRay) ;
    const double eyeRightY   = 0.50 * maxyD - (0.25 * headRay) ;
    const double eyeRay      = 0.30 * headRay;
    const double mouthLeftX  = 0.50 * maxxD - (0.7 * headRay) ;
    const double mouthMidX   = 0.50 * maxxD;
    const double mouthRightX = 0.50 * maxxD + (0.7 * headRay) ;
    const double mouthLeftY  = 0.50 * maxyD + (0.2 * headRay) ;
    const double mouthMidY   = 0.50 * maxyD + (0.7 * headRay) ;
    const double mouthRightY = 0.50 * maxyD + (0.2 * headRay) ;
    //Draw the image on DrawCanvas
    canvas->DrawCircle(midX, midY, headRay);
    canvas->DrawCircle(eyeLeftX, eyeLeftY, eyeRay);
    canvas->DrawDot(eyeLeftX, eyeLeftY);
    canvas->DrawCircle(eyeRightX, eyeRightY, eyeRay);
    canvas->DrawDot(eyeRightX, eyeRightY);
    canvas->DrawLine(mouthLeftX, mouthLeftY, mouthMidX, mouthMidY);
    canvas->DrawLine(mouthMidX, mouthMidY, mouthRightX, mouthRightY);
    canvas->DrawLine(mouthRightX, mouthRightY, mouthLeftX, mouthLeftY);
    {
      std::stringstream s;
      s << (*canvas);
      assert(!s.str().empty());
    }
    canvas->Clear();
    {
      canvas->SetColorSystem(CanvasColorSystem::invert); //Background = Black
      std::stringstream s;
      s << (*canvas);
      const std::string t { s.str() };
      assert(std::count(t.begin(),t.end(),' ') == canvas->GetWidth() * canvas->GetHeight());

    }
  }
  //Saving and loading
  {
    const int maxx = 2;
    const int maxy = 3;
    const boost::shared_ptr<DrawCanvas> canvas(new DrawCanvas(maxx,maxy,CanvasColorSystem::invert));
    canvas->DrawLine(-maxx,-maxy,maxx*2.0,maxy*2.0);

    const boost::shared_ptr<const DrawCanvas> old_canvas {
      new DrawCanvas(canvas->GetGreynesses(),canvas->GetColorSystem(),canvas->GetCoordinatSystem())
    };
    assert( old_canvas !=  canvas);
    assert(*old_canvas == *canvas);

    const std::string temp_filename { fileio::FileIo().GetTempFileName() };
    canvas->Save(temp_filename);
    canvas->Clear();

    assert(*old_canvas != *canvas);

    const boost::shared_ptr<const DrawCanvas> new_canvas {
      new DrawCanvas(temp_filename)
    };

    assert(*old_canvas == *new_canvas);

  }
}
#endif

std::vector<std::string> ribi::DrawCanvas::ToStrings() const noexcept
{
  std::stringstream s;
  s << (*this);
  const auto v = Container().SeperateString(s.str(),'\n');
  return v;
}

std::ostream& ribi::operator<<(std::ostream& os, const DrawCanvas& canvas)
{
  ribi::DrawCanvas::PlotSurface(
    os,
    canvas.m_canvas,
    canvas.m_color_system == ribi::CanvasColorSystem::normal,
    canvas.m_coordinat_system == ribi::CanvasCoordinatSystem::screen);
  return os;
}

bool ribi::operator==(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept
{
  return lhs.m_canvas == rhs.m_canvas
    && lhs.m_color_system == rhs.m_color_system
    && lhs.m_coordinat_system == rhs.m_coordinat_system;
}

bool ribi::operator!=(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept
{
  return !(lhs == rhs);
}

bool ribi::IsAboutEqual(const DrawCanvas& lhs, const DrawCanvas& rhs) noexcept
{
  const bool verbose{false};
  if (lhs.GetColorSystem() != rhs.GetColorSystem())
  {
    if (verbose) { TRACE("Color systems differ"); }
    return false;
  }
  if (lhs.GetCoordinatSystem() != rhs.GetCoordinatSystem())
  {
    if (verbose) { TRACE("Coordinat systems differ"); }
    return false;
  }

  if (lhs.GetGreynesses().size() != rhs.GetGreynesses().size())
  {
    if (verbose) { TRACE("Number of rows differ"); }
    return false;
  }
  const std::size_t n_rows = lhs.GetGreynesses().size();
  for (std::size_t row=0; row!=n_rows; ++row)
  {
    const std::vector<double>& v { lhs.GetGreynesses()[row] };
    const std::vector<double>& w { rhs.GetGreynesses()[row] };
    if (v.size() != w.size())
    {
      if (verbose) { TRACE("Number of columns differ"); }
      return false;
    }
    const std::size_t n_cols = v.size();
    for (std::size_t col=0; col!=n_cols; ++col)
    {
      const double diff = std::abs(v[col] - w[col]);
      if (diff > 0.01)
      {
        if (verbose) { TRACE("Value differs"); }
        return false;
      }
    }
  }
  return true;
}

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict

This page has been created by the tool CodeToHtml