例えば ( -0.1 , 0.1 ) というピクセル座標は書き込まれる際には(0,0)というピクセルになってしまうので、小数点以下の計算結果を目視で確認できない。これをできるクラスを作った。
#pragma once #include <vector> #include <array> struct rgb_t { std::array<unsigned char, 3> data; }; // 実数座標と色情報 struct dpixel_t { double x, y; rgb_t c; dpixel_t(double x_, double y_, rgb_t c_) : x(x_), y(y_), c(c_) {} }; // 一つのピクセル。 // そのピクセルの色と、そのピクセル内に入った実数座標の点が記録できる struct pixel_t { rgb_t color; std::vector< dpixel_t > points; public: bool plot(const double x, const double y,const rgb_t rgba) { if (abs(x) > 0.5) return false; points.emplace_back(x,y, rgba); } void set(rgb_t c) { color = c; } size_t count()const { return points.size(); } }; // 実数型のピクセルを書き込める画像データ型 class CRealPixel { int _width; int _height; std::vector< pixel_t > _img; public: // 画像のピクセル数(整数)を指定 void set_size(int width_,int height_) { _width = width_; _height = height_; _img.resize(_width * _height); } // 画像のピクセル数を取得 size_t size()const { return _width * _height; } //画素へ書き込み(整数のIndex) rgb_t& operator[](const size_t index) { return _img[index].color; } // 画素を取得(二次元) rgb_t& get(const int x, const int y) { return operator[](y * _width + x); } // 実数座標の頂点を追加 void plot(const double x_, const double y_, rgb_t c_) { int ix = round(x_); int iy = round(y_); double x = x_ - ix; double y = y_ - iy; _img[iy * _width + ix].plot(x, y, c_); } // RGBの画像を作成 // まず画素の色で塗りつぶし、その中に実数座標の頂点をプロットする // img_ ... 結果の出力先。RGBの配列。 // ratio_ ... 1ピクセルを何ピクセルで表現するか // width ... img_の画素数。 width = _width * ratio_; // height ... img_の画素数。 height = _height * ratio_; void make_int(std::vector<rgb_t>& img_,int ratio_,int*width,int*height) { int rwidth = _width * ratio_; int rheight = _height * ratio_; *width = rwidth; *height = rheight; img_.resize(rwidth * rheight); for (size_t x = 0; x < _width; x++) { for (size_t y = 0; y < _height; y++) { // このブロックを全着色 for (size_t ax = x*ratio_; ax < (x+1)*ratio_; ax++) { for (size_t ay = y * ratio_; ay < (y + 1) * ratio_; ay++) { img_[ay * rwidth + ax] = _img[y * _width + x].color; } } // このブロック内の小数点以下のピクセルをプロット for (size_t j = 0; j < _img[y * _width + x].count(); j++) { double dx = _img[y * _width + x].points[j].x; double dy = _img[y * _width + x].points[j].y; //このブロックの中央 dx += 0.5; dy += 0.5; int rx = x * ratio_ + (ratio_ * dx); int ry = y * ratio_ + (ratio_ * dy); img_[ry * rwidth + rx] = _img[y * _width + x].points[j].c; } } } } };
#include <iostream> #pragma warning(disable:4996) #include "CRealPixel.hpp" //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// void Bresenham( CRealPixel& img, const int width, const int height, const int sx, const int sy, const int ex, const int ey, const rgb_t color ); //////////////////////////////////////////////////////// void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ); int main() { int width = 100; int height = 40; CRealPixel crp; crp.set_size(width, height); // クリア for (size_t i = 0; i < crp.size(); i++) { crp[i] = rgb_t{ 255,255,255 }; } // ブレゼンハムで書き込み int sx = 5; int sy = 7; int ex = 90; int ey = 30; Bresenham(crp, width, height, sx, sy, ex, ey, { 0,0,0 }); // 浮動小数点型で書き込み double dx = ex - sx; double dy = ey - sy; double a = dy / dx; double b = 7 - a * 5; for (float fx = sx; fx < ex; fx+=0.1) { float fy = a * fx + b; if (round(fx) >= width)break; if (round(fy) >= height)break; crp.plot(fx,fy, { 255,0,0 }); } // ピクセル単位で書き込まれた内容と、実数単位で書き込まれた内容を // RGBの画像へ変換 std::vector<rgb_t> ii; int w, h; crp.make_int(ii, 5, &w, &h); // 変換した画像をファイル保存 ppmP3_write( "aaa.ppm", w, h, &ii[0].data[0], 255 ); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// //! @brief ブレゼンハムの直線描画 //! @param [out] img 画像データ(一次元配列)へのポインタ //! @param [in] width 画像の幅(画素数) //! @param [in] height 画像の高さ(画素数) //! @param [in] sx 線の始点X //! @param [in] sy 線の始点Y //! @param [in] ex 線の終点X //! @param [in] ey 線の終点Y //! @param [in] color 線の色 void Bresenham( CRealPixel& img, const int width, const int height, const int sx, const int sy, const int ex, const int ey, const rgb_t color ) { const int dx = std::abs(ex - sx); const int dy = std::abs(ey - sy); const int nx = (sx < ex) ? 1 : -1; const int ny = (sy < ey) ? 1 : -1; int err = dx - dy; int x = sx; int y = sy; while (1) { if (x >= 0 && y >= 0 && x < width && y < height) { img[y * width + x] = color; } if (x == ex && y == ey) break; const int e2 = 2 * err; if (e2 > -dy) { err = err - dy; x += nx; } if (e2 < dx) { err = err + dx; y += ny; } } } // // // ///////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// //! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }