(C++) ImageCanvas
January 25, 2018 · View on GitHub
(C++) ImageCanvas




ImageCanvas is a Canvas to load images.
Technical facts
./CppImageCanvas/CppImageCanvas.pri
INCLUDEPATH += \ ../../Classes/CppImageCanvas SOURCES += \ ../../Classes/CppImageCanvas/imagecanvas.cpp \ ../../Classes/CppImageCanvas/imagecanvas_test.cpp HEADERS += \ ../../Classes/CppImageCanvas/imagecanvas.h OTHER_FILES += \ ../../Classes/CppImageCanvas/Licence.txt RESOURCES += \ ../../Classes/CppImageCanvas/CppImageCanvas.qrc
./CppImageCanvas/imagecanvas.h
//--------------------------------------------------------------------------- /* ImageCanvas, class to convert an image to ASCII art Copyright 2011-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/CppImageCanvas.htm //--------------------------------------------------------------------------- #ifndef IMAGECANVAS_H #define IMAGECANVAS_H #include <iosfwd> #include <string> #include <vector> #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Weffc++" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #include <boost/checked_delete.hpp> #include <boost/signals2.hpp> #include "canvas.h" #pragma GCC diagnostic pop struct QImage; namespace ribi { ///ImageCanvas converts an image to a Canvas struct ImageCanvas : public Canvas { ImageCanvas() noexcept; ///The number of characters the Canvas is heigh and wide ///but also the maximum x and y coordinat. The minimum ///x and y coordinats are 0.0 and 0.0 ImageCanvas( const std::string& filename, const int n_cols, const CanvasColorSystem colorSystem = CanvasColorSystem::normal, const CanvasCoordinatSystem coordinatSystem = CanvasCoordinatSystem::screen ) noexcept; ImageCanvas(const ImageCanvas&) noexcept; ImageCanvas& operator=(const ImageCanvas&) noexcept; ~ImageCanvas() = default; ///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; } int GetHeight() const noexcept override; static std::string GetVersion() noexcept; static std::vector<std::string> GetVersionHistory() noexcept; int GetWidth() const noexcept override { return m_n_cols; } ///Set the color system used void SetColorSystem(const CanvasColorSystem color_system) noexcept; ///Set the coordinat system used void SetCoordinatSystem(const CanvasCoordinatSystem coordinat_system) noexcept; #ifndef NDEBUG static void Test() noexcept; #endif std::vector<std::string> ToStrings() const noexcept override; private: ///Canvas is the original image its greynesses const 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; ///Number of columns const int m_n_cols; ///Returns a Y-X-ordered std::vector of greynesses, with the same size as the original image ///For example: /// /// a three pixel gradient line -> {0.0, 0.5, 1.0 } /// static std::vector<std::vector<double> > ConvertToGreyYx(const QImage * const i) noexcept; ///Returns a Y-X-ordered std::vector of greynesses, with the same size as the original image static std::vector<std::vector<double> > ConvertToGreyYx(const std::string& filename) noexcept; ///Converts a Y-X-ordered std::vector of greynesses ///to a text with a certain number of columns, ///For example: /// /// {0.0, 0.5, 1.0} -> " nM" /// ///'greynesses' must be a y-x-ordered std::vector of grey values ///ranging from [0.0,1.0], where 0.0 denotes black and ///1.0 denotes white. ///From http://www.richelbilderbeek.nl/CppImageToAscii.htm static std::vector<std::string> ConvertGreynessesToAscii(const std::vector<std::vector<double> >& greynesses, const int n_cols) noexcept; ///Generalizes a pixel, line or rectangle to one average greyness static double GetFractionGrey( const std::vector<std::vector<double> >& image, const int x1, const int y1, const int x2, const int y2) noexcept; /// static double GetGreyness( const std::vector<std::vector<double> >& image, const int x, const int y) noexcept; static double GetGreyness( const std::vector<std::vector<double> >& image, const int x1, const int x2, const int y) noexcept; //Get a square of pixels' average greyness static double GetGreyness( const std::vector<std::vector<double> >& image, const int x1, const int y1, const int x2, const int y2) noexcept; friend std::ostream& operator<<(std::ostream& os, const ImageCanvas& canvas) noexcept; }; std::ostream& operator<<(std::ostream& os, const ImageCanvas& canvas) noexcept; bool operator==(const ImageCanvas& lhs, const ImageCanvas& rhs); bool operator!=(const ImageCanvas& lhs, const ImageCanvas& rhs); } //~namespace ribi #endif
./CppImageCanvas/imagecanvas.cpp
//--------------------------------------------------------------------------- /* ImageCanvas, class to convert an image to ASCII art Copyright 2011-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/CppImageCanvas.htm //--------------------------------------------------------------------------- #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Weffc++" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #include "imagecanvas.h" #include <iostream> #include <cassert> #include <cmath> #include <algorithm> #include <functional> #include <iterator> #include <sstream> #include <boost/math/constants/constants.hpp> #include <QFile> #include <QImage> #include "canvascolorsystems.h" #include "canvascoordinatsystems.h" #include "fileio.h" #include "trace.h" #include "testtimer.h" #pragma GCC diagnostic pop ribi::ImageCanvas::ImageCanvas() noexcept : m_canvas{}, m_color_system{}, m_coordinat_system{}, m_n_cols{0} { #ifndef NDEBUG Test(); #endif } ribi::ImageCanvas::ImageCanvas( const std::string& filename, const int n_cols, const ribi::CanvasColorSystem colorSystem, const ribi::CanvasCoordinatSystem coordinatSystem) noexcept : m_canvas{ConvertToGreyYx(filename)}, m_color_system(colorSystem), m_coordinat_system(coordinatSystem), m_n_cols(n_cols) { #ifndef NDEBUG Test(); #endif } ribi::ImageCanvas::ImageCanvas( const ImageCanvas& other) noexcept : m_canvas{other.m_canvas}, m_color_system{other.m_color_system}, m_coordinat_system{other.m_coordinat_system}, m_n_cols{other.m_n_cols} { } ribi::ImageCanvas& ribi::ImageCanvas::operator=( const ImageCanvas& other) noexcept { const_cast<std::vector<std::vector<double>>&>(m_canvas) = other.m_canvas; //m_canvas = other.m_canvas; m_color_system = other.m_color_system; m_coordinat_system = other.m_coordinat_system; const_cast<int&>(m_n_cols) = other.m_n_cols; return *this; } std::vector<std::string> ribi::ImageCanvas::ConvertGreynessesToAscii( const std::vector<std::vector<double> >& image, const int width //How many chars the ASCII image will be wide ) noexcept { if (width == 0) return std::vector<std::string>{}; //If the number of chars is below 5, //the calculation would be more complicated due to a too trivial value of charWidth assert(width >= 5); std::vector<std::string> v; if (image.empty()) return v; assert(!image.empty()); //Maxy is in proportion with the bitmap const int image_width = image[0].size(); const int image_height = image.size(); const int maxy = (static_cast<double>(width) / static_cast<double>(image_width)) * static_cast<double>(image_height) / 1.5; //Characters are 1.5 higher than wide assert(maxy > 0); const double dX = static_cast<double>(image_width) / static_cast<double>(width); const double dY = static_cast<double>(image_height) / static_cast<double>(maxy); assert(dX > 0.0); assert(dY > 0.0); for (int y=0; y!=maxy; ++y) { std::string s; for (int x=0; x!=width; ++x) { const int x1 = std::min( static_cast<double>(x) * dX, image_width - 1.0) + 0.5; const int y1 = std::min( static_cast<double>(y) * dY, image_height - 1.0) + 0.5; const int x2 = std::min( (static_cast<double>(x) * dX) + dX, image_width - 1.0) + 0.5; const int y2 = std::min( (static_cast<double>(y) * dY) + dY, image_height - 1.0) + 0.5; assert(x1 >= 0); assert(x2 >= 0); assert(y1 >= 0); assert(y2 >= 0); assert(x1 < image_width); assert(x2 < image_width); assert(y1 < image_height); assert(y2 < image_height); const double f = GetFractionGrey(image,x1,y1,x2,y2); assert(f >= 0.0 && f <= 1.0); const std::vector<char> m_gradient { GetAsciiArtGradient() }; const int i = boost::numeric_cast<int>( f * boost::numeric_cast<double>(m_gradient.size() - 1)); assert(i >= 0); assert(i < boost::numeric_cast<int>(m_gradient.size())); const char c = m_gradient[i]; s+=c; } v.push_back(s); } return v; } std::vector<std::vector<double>> ribi::ImageCanvas::ConvertToGreyYx(const QImage * const i) noexcept { const int maxy = i->height(); const int maxx = i->width(); std::vector<std::vector<double> > v; if (maxx == 0) return v; assert(maxx > 0); const int n_bytes = i->bytesPerLine() / maxx; for (int y=0; y!=maxy; ++y) { v.push_back(std::vector<double>()); const unsigned char * const line = i->scanLine(y); for (int x=0; x!=maxx; ++x) { int sum = 0; for (int byte=0; byte!=n_bytes; ++byte) { sum += line[(x * n_bytes) + byte]; } const double greyness = (boost::numeric_cast<double>(sum) / boost::numeric_cast<double>(n_bytes)) / 256.0; assert(greyness >= 0.0); assert(greyness <= 1.0); v.back().push_back(greyness); } } return v; } std::vector<std::vector<double>> ribi::ImageCanvas::ConvertToGreyYx(const std::string& filename) noexcept { const boost::scoped_ptr<QImage> qimage{ new QImage(filename.c_str()) }; assert(qimage); return ConvertToGreyYx(qimage.get()); } double ribi::ImageCanvas::GetFractionGrey( const std::vector<std::vector<double> >& image, const int x1, const int y1, const int x2, const int y2) noexcept { assert(x1 <= x2); assert(y1 <= y2); if (x1 == x2 && y1 == y2) return GetGreyness(image,x1,y1); if (y1 == y2) return GetGreyness(image,x1,x2,y1); if (x1 == x2) { assert(y1 < y2); double sum = 0; for (int y=y1; y!=y2; ++y) { const double g = GetGreyness(image,x1,y); assert(g >= 0.0); assert(g <= 1.0); sum+=g; } const double average_greyness = sum / boost::numeric_cast<double>(y2-y1); assert(average_greyness >= 0.0); assert(average_greyness <= 1.0); return average_greyness; } return GetGreyness(image,x1,y1,x2,y2); } double ribi::ImageCanvas::GetGreyness( const std::vector<std::vector<double> >& image, const int x, const int y) noexcept { assert(!image.empty() && "Image is NULL"); assert(x >= 0 && "x coordinat is below zero"); assert(y >= 0 && "y coordinat is below zero"); assert(y < boost::numeric_cast<int>(image.size()) && "y coordinat is beyond image height"); assert(x < boost::numeric_cast<int>(image[y].size()) && "x coordinat is beyond image width"); const double greyness = image[y][x]; assert(greyness >= 0.0); assert(greyness <= 1.0); return greyness; } double ribi::ImageCanvas::GetGreyness( const std::vector<std::vector<double> >& image, const int x1, const int x2, const int y) noexcept { assert(!image.empty() && "Image is NULL"); assert(x1 >= 0 && "x1 coordinat is below zero"); assert(x2 >= 0 && "x2 coordinat is below zero"); assert(y >= 0 && "y coordinat is below zero"); assert(y < boost::numeric_cast<int>(image.size()) && "y coordinat is beyond image height"); assert(x1 < x2 && "X-coordinats must be different and ordered"); assert(x1 < boost::numeric_cast<int>(image[y].size()) && "x1 coordinat is beyond image width"); assert(x2 < boost::numeric_cast<int>(image[y].size()) && "x2 coordinat is beyond image width"); assert(image[y].begin() + x2 != image[y].end() && "x2 coordinat iterator must not be beyond image width"); const double average_greyness = std::accumulate( image[y].begin() + x1, image[y].begin() + x2, 0.0) / boost::numeric_cast<double>(x2-x1); assert(average_greyness >= 0.0); assert(average_greyness <= 1.0); return average_greyness; } //Get a square of pixels' average greyness double ribi::ImageCanvas::GetGreyness( const std::vector<std::vector<double> >& image, const int x1, const int y1, const int x2, const int y2) noexcept { assert(y1 < y2 && "Y-coordinats must be ordered"); double sum = 0.0; for (int y=y1; y!=y2; ++y) { const double grey = GetGreyness(image,x1,x2,y); assert(grey >= 0 && grey < 1.0); sum+=grey; } const double average_greyness = sum / boost::numeric_cast<double>(y2 - y1); assert(average_greyness >=0.0 && average_greyness <= 1.0); return average_greyness; } int ribi::ImageCanvas::GetHeight() const noexcept { return static_cast<int>(m_canvas.size()); } std::string ribi::ImageCanvas::GetVersion() noexcept { return "4.0"; } std::vector<std::string> ribi::ImageCanvas::GetVersionHistory() noexcept { return { "2011-03-23: Version 1.0: initial version, then called AsciiArter", "2014-01-07: Version 2.0: add conversion to Canvas" "2014-01-07: version 3.0: reworked interface, renamed to ImageCanvas", "2015-07-20: version 4.0: characters are not square" }; } void ribi::ImageCanvas::SetColorSystem(const CanvasColorSystem colorSystem) noexcept { if (this->m_color_system != colorSystem) { this->m_color_system = colorSystem; this->m_signal_changed(this); } } void ribi::ImageCanvas::SetCoordinatSystem(const CanvasCoordinatSystem coordinatSystem) noexcept { if (this->m_coordinat_system != coordinatSystem) { this->m_coordinat_system = coordinatSystem; this->m_signal_changed(this); } } std::vector<std::string> ribi::ImageCanvas::ToStrings() const noexcept { std::vector<std::vector<double>> canvas { m_canvas }; if (m_color_system == CanvasColorSystem::invert) { for (auto& line: canvas) { for (double& x: line) { x = 1.0 - x; } } } std::vector<std::string> text { ConvertGreynessesToAscii(canvas,m_n_cols) }; if (m_coordinat_system == CanvasCoordinatSystem::graph) { std::reverse(text.begin(),text.end()); } return text; } std::ostream& ribi::operator<<(std::ostream& os, const ImageCanvas& canvas) noexcept { const auto v = canvas.ToStrings(); std::copy(v.begin(),v.end(),std::ostream_iterator<std::string>(os,"\n")); return os; } bool ribi::operator==(const ImageCanvas& lhs, const ImageCanvas& rhs) { return lhs.ToStrings() == rhs.ToStrings(); } bool ribi::operator!=(const ImageCanvas& lhs, const ImageCanvas& rhs) { return !(lhs == rhs); }
./CppImageCanvas/imagecanvas_test.cpp
//--------------------------------------------------------------------------- /* ImageCanvas, class to convert an image to ASCII art Copyright 2011-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/CppImageCanvas.htm //--------------------------------------------------------------------------- #ifndef NDEBUG #include "imagecanvas.h" #include <iostream> #include <cassert> #include <cmath> #include <algorithm> #include <functional> #include <iterator> #include <sstream> #include <boost/math/constants/constants.hpp> #include <QFile> #include <QImage> #include "canvascolorsystems.h" #include "canvascoordinatsystems.h" #include "fileio.h" #include "trace.h" #include "testtimer.h" #include "imagecanvas.h" void ribi::ImageCanvas::Test() noexcept { { static bool is_tested{false}; if (is_tested) return; is_tested = true; } { fileio::FileIo(); CanvasColorSystems(); CanvasCoordinatSystems(); } const TestTimer test_timer(__func__,__FILE__,1.0); const std::string temp_filename { fileio::FileIo().GetTempFileName() }; { const std::string resource_filename { ":/CppImageCanvas/images/R.png" }; QFile qfile(resource_filename.c_str()); qfile.copy(temp_filename.c_str()); if (!fileio::FileIo().IsRegularFile(temp_filename)) { TRACE("ERROR"); TRACE(resource_filename); TRACE("Resource filename must exist"); } } assert(fileio::FileIo().IsRegularFile(temp_filename)); const int n = static_cast<int>(CanvasColorSystems().GetAll().size()) * static_cast<int>(CanvasCoordinatSystems().GetAll().size()); for (int i=0; i!=n; ++i) { const int ncs = static_cast<int>(CanvasColorSystems().GetAll().size()); const int a = i % ncs; const CanvasColorSystem color_system = CanvasColorSystems().GetAll()[a]; const int b = i / ncs; const CanvasCoordinatSystem coordinat_system = CanvasCoordinatSystems().GetAll()[b]; const ImageCanvas c(temp_filename,20,color_system,coordinat_system); std::stringstream s; s << c; assert(!s.str().empty()); //TRACE(c); } { const ImageCanvas a(temp_filename,60); const ImageCanvas b(temp_filename,60); assert(a == b); } { const ImageCanvas a(temp_filename,60); ImageCanvas b(temp_filename,50); assert(a != b); b = a; assert(a == b); } fileio::FileIo().DeleteFile(temp_filename); } #endif