以前書いた奴と同じだが自分で読んでわかりにくかったので書き直し。
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkDoubleArray.h> #include <vtkPointData.h> #include <vtkActor.h> #include <vtkCellArray.h> #include <vtkPoints.h> #include <vtkPolyData.h> #include <vtkPolyDataMapper.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle);
void create( vtkSmartPointer<vtkPoints> points, vtkSmartPointer<vtkDoubleArray> colors, vtkSmartPointer<vtkCellArray> cells ) { for (size_t i = 0; i < 100000; i++) { double x = (double)rand() / (double)RAND_MAX; double y = (double)rand() / (double)RAND_MAX; double z = (double)rand() / (double)RAND_MAX; points->InsertNextPoint(x, y, z); // 座標追加 colors->InsertNextTuple3(x, y, z); // 色情報追加 cells->InsertNextCell(1); // 要素数 cells->InsertCellPoint(i); // 頂点インデックスを追加 } }
vtkSmartPointer<vtkActor> createActor() { // 頂点配列 vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New(); // 色情報の配列 vtkSmartPointer<vtkDoubleArray> colors = vtkSmartPointer<vtkDoubleArray>::New(); colors->SetNumberOfComponents(3); colors->SetName("Colors"); // 頂点インデクスの配列 vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New(); create(points, colors, cells); /////////////////////////////////////////////////// // --- vtkPolyData作成 --- auto polydata = vtkSmartPointer<vtkPolyData>::New(); polydata->SetPoints(points); polydata->SetVerts(cells); polydata->GetPointData()->SetScalars(colors); // --- actor作成 --- auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(polydata); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); return actor; }
int main(int /*argc*/, char** /*argv*/) { auto actor = createActor(); auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }
Open3DのOBB。GetBoxPoints()のコメントに各頂点のindexが載っているのでそれを参考にすればLineSetで表示できる。
#include <Windows.h> #include <open3d/Open3D.h> #include <GL/GL.h> #pragma comment(lib, "opengl32.lib") std::shared_ptr<open3d::geometry::LineSet> obb_to_lineset(const open3d::geometry::OrientedBoundingBox& obb) { // BoundingVolume.h 参照 /// 0 ------------------- 1 /// /| /| /// / | / | /// / | / | /// / | / | /// 2 ------------------- 7 | /// | |____________|____| 6 /// | /3 | / /// | / | / /// | / | / /// |/ |/ /// 5 ------------------- 4 std::vector<Eigen::Vector3d> points = obb.GetBoxPoints(); std::vector<Eigen::Vector2i> linesegments; // 0-2-7-1 linesegments.push_back(Eigen::Vector2i(0,2)); linesegments.push_back(Eigen::Vector2i(2,7)); linesegments.push_back(Eigen::Vector2i(7,1)); linesegments.push_back(Eigen::Vector2i(1,0)); // 4-5-3-6 linesegments.push_back(Eigen::Vector2i(4,5)); linesegments.push_back(Eigen::Vector2i(5,3)); linesegments.push_back(Eigen::Vector2i(3,6)); linesegments.push_back(Eigen::Vector2i(6,4)); // 0-3-5-2 linesegments.push_back(Eigen::Vector2i(0,3)); linesegments.push_back(Eigen::Vector2i(3,5)); linesegments.push_back(Eigen::Vector2i(5,2)); linesegments.push_back(Eigen::Vector2i(2,0)); // 4-6-1-7 linesegments.push_back(Eigen::Vector2i(4,6)); linesegments.push_back(Eigen::Vector2i(6,1)); linesegments.push_back(Eigen::Vector2i(1,7)); linesegments.push_back(Eigen::Vector2i(7,4)); // 色を設定 std::vector<Eigen::Vector3d> colors; for (size_t i = 0; i < linesegments.size(); ++i) { colors.push_back(Eigen::Vector3d(0.0, 0.0, 0.0)); } auto line_set = std::make_shared<open3d::geometry::LineSet>(); line_set->points_ = points; line_set->lines_ = linesegments; line_set->colors_ = colors; return line_set; } int main() { // 点群データの作成 auto cloud = std::make_shared<open3d::geometry::PointCloud>(); // ランダムな点を生成 for (int i = 0; i < 50000; ++i) { double x = (static_cast<double>(rand()) / RAND_MAX) * 80.0; double y = (static_cast<double>(rand()) / RAND_MAX) * 30.0; double z = (static_cast<double>(rand()) / RAND_MAX) * 10.0; cloud->points_.push_back(Eigen::Vector3d(x, y, z)); } auto lineset = obb_to_lineset(cloud->GetOrientedBoundingBox()); // 表示用のウィンドウを作成 open3d::visualization::Visualizer visualizer; visualizer.CreateVisualizerWindow("Open3D Visualization", 800, 600); visualizer.AddGeometry(cloud); visualizer.AddGeometry(lineset); visualizer.UpdateGeometry(); visualizer.PollEvents(); visualizer.UpdateRender(); // ウィンドウを閉じるまでループ while (visualizer.PollEvents()) { visualizer.UpdateRender(); } visualizer.DestroyVisualizerWindow(); return 0; }
VTM、VTPはXML形式なので、VTKを使わなくても出力できる。自分で作ったデータをvtm,vtpで出力するとparaview等で可視化できる。
XMLなのでpugixmlを使用。
save_vtm_vtkfreeで.vtmファイルを保存する。
#pragma once #include <Eigen/Dense> #include <filesystem> #include <vector> #include <string> #include "pugixml.hpp" struct Data3D { std::vector< Eigen::Vector3d > points; std::vector< Eigen::Vector3i > triangles; // 頂点ID三つで構成される三角形 std::vector< int > polyline;// N個の頂点を格納した頂点リストで表現される折れ線 };
void save_vtp_vtkfree(const std::string& filename, const Data3D& data) { pugi::xml_document doc; auto vtkFile = doc.append_child("VTKFile"); vtkFile.append_attribute("type") = "PolyData"; vtkFile.append_attribute("version") = "1.0"; vtkFile.append_attribute("byte_order") = "LittleEndian"; auto polyData = vtkFile.append_child("PolyData"); polyData.append_attribute("WholeExtent") = "0 0 0 0 0 0"; auto piece = polyData.append_child("Piece"); piece.append_attribute("NumberOfPoints") = data.points.size(); piece.append_attribute("NumberOfLines") = data.polyline.empty() ? 0 : 1; piece.append_attribute("NumberOfPolys") = data.triangles.size(); ////////////////////////////////////////////////////////////////// // Points auto pointsNode = piece.append_child("Points"); auto pointsArray = pointsNode.append_child("DataArray"); pointsArray.append_attribute("type") = "Float64"; pointsArray.append_attribute("NumberOfComponents") = 3; pointsArray.append_attribute("format") = "ascii"; std::ostringstream pointsStream; for (const auto& p : data.points) { pointsStream << p.x() << " " << p.y() << " " << p.z() << " "; } pointsArray.text().set(pointsStream.str().c_str()); ////////////////////////////////////////////////////////////////// // PolyLine if (!data.polyline.empty()) { auto linesNode = piece.append_child("Lines"); auto connArray = linesNode.append_child("DataArray"); connArray.append_attribute("type") = "Int32"; connArray.append_attribute("Name") = "connectivity"; connArray.append_attribute("format") = "ascii"; std::ostringstream connStream; for (const auto& pointIndex : data.polyline) { connStream << pointIndex << " "; } connArray.text().set(connStream.str().c_str()); auto offsetArray = linesNode.append_child("DataArray"); offsetArray.append_attribute("type") = "Int32"; offsetArray.append_attribute("Name") = "offsets"; offsetArray.append_attribute("format") = "ascii"; std::ostringstream offsetStream; offsetStream << data.polyline.size(); offsetArray.text().set(offsetStream.str().c_str()); } ////////////////////////////////////////////////////////////////// // Triangles if (!data.triangles.empty()) { auto polysNode = piece.append_child("Polys"); auto polyConnArray = polysNode.append_child("DataArray"); polyConnArray.append_attribute("type") = "Int32"; polyConnArray.append_attribute("Name") = "connectivity"; polyConnArray.append_attribute("format") = "ascii"; std::ostringstream polyConnStream; for (const auto& tri : data.triangles) { polyConnStream << tri[0] << " " << tri[1] << " " << tri[2] << " "; } polyConnArray.text().set(polyConnStream.str().c_str()); auto polyOffsetArray = polysNode.append_child("DataArray"); polyOffsetArray.append_attribute("type") = "Int32"; polyOffsetArray.append_attribute("Name") = "offsets"; polyOffsetArray.append_attribute("format") = "ascii"; std::ostringstream polyOffsetStream; for (size_t i = 0; i < data.triangles.size(); ++i) { polyOffsetStream << (i + 1) * 3 << " "; } polyOffsetArray.text().set(polyOffsetStream.str().c_str()); } ////////////////////////////////////////////////////////////////// doc.save_file(filename.c_str()); }
// VTKの形式であるVTMファイルを保存する関数 // ただしVTKライブラリを使用しない void save_vtm_vtkfree(const std::string& filename, const std::vector<Data3D>& dataList) { // filenameからディレクトリ名を取得 std::filesystem::path path(filename); std::string directory = path.parent_path().string(); std::string filenameWithoutExtension = path.stem().string(); if( directory.empty() ) { directory = "."; } std::string vtpdirectory = directory + "/" + filenameWithoutExtension; // ディレクトリが存在しない場合は作成存在する場合は中を空にする if (std::filesystem::exists(vtpdirectory)) { for (const auto& entry : std::filesystem::directory_iterator(vtpdirectory)) { std::filesystem::remove_all(entry.path()); } } std::filesystem::create_directories(vtpdirectory); pugi::xml_document doc; auto vtkFile = doc.append_child("VTKFile"); vtkFile.append_attribute("type") = "vtkMultiBlockDataSet"; vtkFile.append_attribute("version") = "1.0"; vtkFile.append_attribute("byte_order") = "LittleEndian"; auto mbNode = vtkFile.append_child("vtkMultiBlockDataSet"); for (size_t i = 0; i < dataList.size(); ++i) { std::string vtpFilename = filenameWithoutExtension + "_" + std::to_string(i) + ".vtp"; save_vtp_vtkfree(vtpdirectory + "/" + vtpFilename, dataList[i]); auto dataSetNode = mbNode.append_child("DataSet"); dataSetNode.append_attribute("index") = static_cast<int>(i); dataSetNode.append_attribute("file") = (filenameWithoutExtension + "/" + vtpFilename).c_str(); } doc.save_file(filename.c_str()); }
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkActor.h> #include <vtkPolyData.h> #include <vtkPolyDataMapper.h> #include <vtkCellArray.h> #include <vtkPoints.h> #include <vtkLine.h> #include <vtkTriangle.h> #include <vtkProperty.h> #include <vtkXMLPolyDataReader.h> #include <vtkXMLMultiBlockDataReader.h> #include <vtkMultiBlockDataSet.h> //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); #include "save_vtm_vtkfree.hpp" // メッシュデータの生成関数 Data3D createSampleData(); void displayVTP(const std::string& filename, bool showLines, bool showPolys, double lineWidth, double lineColor[3], double polyColor[3]); int main(int /*argc*/, char** /*argv*/) { Data3D data = createSampleData(); Data3D data2 = createSampleData(); for(auto& p : data2.points){ p.x() += 1.0; p.y() += 1.0; p.z() += 1.0; } //データをVTK形式で保存 std::vector<Data3D> dataList; dataList.push_back(data); dataList.push_back(data2); save_vtm_vtkfree("C:\\test\\test2.vtm", dataList); double c[3] = { 1.0, 0.0, 0.0 }, d[3] = { 0.0, 1.0, 0.0 }; displayVTP("C:\\test\\test2.vtm", true, true, 5.0, c,d); return 0; } Data3D createSampleData() { Data3D data; // 点群の生成 data.points.push_back(Eigen::Vector3d(0.0, 0.0, 0.0)); data.points.push_back(Eigen::Vector3d(1.0, 0.0, 0.0)); data.points.push_back(Eigen::Vector3d(1.0, 1.0, 0.0)); data.points.push_back(Eigen::Vector3d(0.0, 1.0, 0.0)); data.points.push_back(Eigen::Vector3d(0.5, 0.5, 1.0)); // 三角形の生成 (底面と頂点を結ぶ三角形) data.triangles.push_back(Eigen::Vector3i(0, 1, 4)); data.triangles.push_back(Eigen::Vector3i(1, 2, 4)); data.triangles.push_back(Eigen::Vector3i(2, 3, 4)); data.triangles.push_back(Eigen::Vector3i(3, 0, 4)); data.triangles.push_back(Eigen::Vector3i(0, 1, 2)); data.triangles.push_back(Eigen::Vector3i(0, 2, 3)); // 折れ線の生成 data.polyline = { 0, 1, 2, 3, 0, }; return data; } void displayVTP(const std::string& filename, bool showLines, bool showPolys, double lineWidth, double lineColor[3], double polyColor[3]) { vtkSmartPointer<vtkXMLMultiBlockDataReader> reader = vtkSmartPointer<vtkXMLMultiBlockDataReader>::New(); reader->SetFileName(filename.c_str()); reader->Update(); vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->SetBackground(0.1, 0.1, 0.1); vtkMultiBlockDataSet* multiBlockData = vtkMultiBlockDataSet::SafeDownCast(reader->GetOutput()); if (!multiBlockData) return; for (unsigned int i = 0; i < multiBlockData->GetNumberOfBlocks(); ++i) { vtkSmartPointer<vtkPolyData> polyData = vtkPolyData::SafeDownCast(multiBlockData->GetBlock(i)); if (!polyData) continue; if (showLines && polyData->GetLines()->GetNumberOfCells() > 0) { vtkSmartPointer<vtkPolyDataMapper> lineMapper = vtkSmartPointer<vtkPolyDataMapper>::New(); lineMapper->SetInputData(polyData); vtkSmartPointer<vtkActor> lineActor = vtkSmartPointer<vtkActor>::New(); lineActor->SetMapper(lineMapper); lineActor->GetProperty()->SetColor(lineColor); lineActor->GetProperty()->SetLineWidth(lineWidth); renderer->AddActor(lineActor); } if (showPolys && polyData->GetPolys()->GetNumberOfCells() > 0) { vtkSmartPointer<vtkPolyDataMapper> polyMapper = vtkSmartPointer<vtkPolyDataMapper>::New(); polyMapper->SetInputData(polyData); vtkSmartPointer<vtkActor> polyActor = vtkSmartPointer<vtkActor>::New(); polyActor->SetMapper(polyMapper); polyActor->GetProperty()->SetColor(polyColor); polyActor->GetProperty()->SetEdgeVisibility(false); renderer->AddActor(polyActor); } } vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); interactor->SetRenderWindow(renderWindow); renderWindow->Render(); interactor->Start(); }
test2.vtm
<?xml version="1.0"?> <VTKFile type="vtkMultiBlockDataSet" version="1.0" byte_order="LittleEndian"> <vtkMultiBlockDataSet> <DataSet index="0" file="test2/test2_0.vtp" /> <DataSet index="1" file="test2/test2_1.vtp" /> </vtkMultiBlockDataSet> </VTKFile>
test2/test2_0.vtp
<?xml version="1.0"?> <VTKFile type="PolyData" version="1.0" byte_order="LittleEndian"> <PolyData WholeExtent="0 0 0 0 0 0"> <Piece NumberOfPoints="5" NumberOfLines="1" NumberOfPolys="6"> <Points> <DataArray type="Float64" NumberOfComponents="3" format="ascii">0 0 0 1 0 0 1 1 0 0 1 0 0.5 0.5 1 </DataArray> </Points> <Lines> <DataArray type="Int32" Name="connectivity" format="ascii">0 1 2 3 0 </DataArray> <DataArray type="Int32" Name="offsets" format="ascii">5</DataArray> </Lines> <Polys> <DataArray type="Int32" Name="connectivity" format="ascii">0 1 4 1 2 4 2 3 4 3 0 4 0 1 2 0 2 3 </DataArray> <DataArray type="Int32" Name="offsets" format="ascii">3 6 9 12 15 18 </DataArray> </Polys> </Piece> </PolyData> </VTKFile>
何度か作っているが少し真面目に作った。wxSizer等は使っていない。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include "MyItemPanel.hpp" //! @brief 自作リストコントロール class MyObjectListWidget : public wxPanel { wxPanel* _viewArea; //!< リスト表示エリア wxScrollBar* _scrollBar; //!< スクロールバー int _itemHeight; //!< 一つのアイテムの高さ //! @brief 作成したパネルのポインタ一覧 std::vector< ItemPanel* > _createdPanels; //! @brief パネルを生成するオブジェクト std::shared_ptr<MyObjectListItemAccessor> _itemaccessor; int _activedItemIndex; //!< 現在アクティブなアイテムのインデックス //!< パネルを表示 void SetItemPanels(); //!< ウィジェット生成直後の動作 void PostCreate(); public: MyObjectListWidget( wxWindow* parent, std::shared_ptr<MyObjectListItemAccessor> controller, int panelheight); //! @brief パネル一枚の高さを取得 int GetPanelHeight()const { return _itemHeight; } //! @brief panelindex番のパネルが画面から見切れていたら、それが見えるように全体のパネルの配置をずらして調節する void FitPosition(int panelindex); //! @brief アクティブなアイテムを指定 void SetActiveItemIndex(int itemindex) { _activedItemIndex = itemindex; } //! @brief アクティブなアイテムを取得 int GetActiveItemIndex()const { return _activedItemIndex; } //! @brief アイテムの総数を取得 int Count()const; //! @brief 表示可能なアイテム数を計算 int GetDisplayableItemCount()const; //! @brief パネルの表示状態の更新 // 使用するパネルのみ表示し、表示するパネルのアイテム番号を指定し、そのパネルの表示内容を更新する void UpdateItemPanelList(); //! @brief サイズ変更イベント void OnSize(wxSizeEvent& event); //! @brief スクロール void OnScroll(wxScrollEvent& event); };
#include "MyObjectListWidget.hpp" // アイテムの総数を取得 int MyObjectListWidget::Count()const { return _itemaccessor->Size(); } // 表示可能なアイテム数を計算 int MyObjectListWidget::GetDisplayableItemCount()const { return _viewArea->GetSize().y / GetPanelHeight() + 1; } // パネルを表示 void MyObjectListWidget::SetItemPanels() { const int displayable = GetDisplayableItemCount(); const int created = _createdPanels.size(); const int panelHeight = GetPanelHeight(); if (created == displayable) { ; } else if (created < displayable) { for (size_t i = 0; i < created; i++) { _createdPanels[i]->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); if (!_createdPanels[i]->IsShown()) { _createdPanels[i]->Show(); } } // 表示可能なパネル数>定義済みパネル数の場合は // 足りていない分のパネルを追加 for (size_t i = created; i < displayable; i++) { ItemPanel* itemPanel = _itemaccessor->CreatePanel(_viewArea); itemPanel->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); _createdPanels.push_back(itemPanel); } } else { // 表示可能なパネル数<定義済みパネル数の場合は // 余ったパネルを非表示 for (size_t i = displayable; i < created; i++) { _createdPanels[i]->Hide(); } } // スクロールバーの現在位置 const int scrollpos = _scrollBar->GetThumbPosition(); // 表示しているパネル全てについての処理 // サイズのみ更新 for (size_t i = 0; i < displayable; i++) { _createdPanels[i]->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); if (!_createdPanels[i]->IsShown()) { _createdPanels[i]->Show(); } _createdPanels[i]->SetPanelInfo(this,i, scrollpos + i); _createdPanels[i]->UpdatePanel(); } } void MyObjectListWidget::FitPosition(int panelindex) { const int panelHeight = GetPanelHeight(); int fixpixel = 0; // アイテムの位置が上方向にはみ出している場合 if(_createdPanels[panelindex]->GetPosition().y < 0){ // 修正するピクセル数を計算 fixpixel = _createdPanels[panelindex]->GetPosition().y; } // アイテムの位置が下方向にはみ出している場合 else if (_createdPanels[panelindex]->GetPosition().y + panelHeight > _viewArea->GetSize().y) { // 修正するピクセル数を計算 fixpixel = _createdPanels[panelindex]->GetPosition().y + panelHeight - _viewArea->GetSize().y; } // 全てのパネルの位置を修正 for (size_t i = 0; i < _createdPanels.size(); i++) { _createdPanels[i]->SetPosition( wxPoint( _createdPanels[i]->GetPosition().x, _createdPanels[i]->GetPosition().y - fixpixel)); } // 必要な部分だけを再描画 _viewArea->Refresh(); _viewArea->Update(); } void MyObjectListWidget::UpdateItemPanelList() { SetItemPanels(); _viewArea->Layout(); } void MyObjectListWidget::PostCreate() { UpdateItemPanelList(); _scrollBar->SetScrollbar( 0, GetDisplayableItemCount(), _itemaccessor->Size(), 5, true); } MyObjectListWidget::MyObjectListWidget( wxWindow* parent, std::shared_ptr<MyObjectListItemAccessor> controller, int panelheight) : wxPanel(parent, wxID_ANY), _itemaccessor(controller), _itemHeight(panelheight) { // レイアウトマネージャーを作成 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); // ViewAreaパネルを作成して追加 _viewArea = new wxPanel(this, wxID_ANY); sizer->Add(_viewArea, 1, wxEXPAND); //_viewAreaの背景を黒 _viewArea->SetBackgroundColour(wxColour(0, 0, 0)); // スクロールバーを作成して追加 _scrollBar = new wxScrollBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL); sizer->Add(_scrollBar, 0, wxEXPAND); // レイアウトを設定 SetSizer(sizer); // サイズ変更時に表示内容の確認と更新 Bind(wxEVT_SIZE, &MyObjectListWidget::OnSize, this); // スクロールイベント Bind(wxEVT_SCROLL_CHANGED, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_LINEUP, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_LINEDOWN, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_THUMBTRACK, &MyObjectListWidget::OnScroll, this); CallAfter(&MyObjectListWidget::PostCreate); Layout(); } // サイズ変更イベント void MyObjectListWidget::OnSize(wxSizeEvent& event) { // ウィンドウの高さが変わった場合、表示可能データ件数が変わり、 // スクロールバーの最大値が変わるため、再設定 // このとき、現在の表示位置を変えないように注意する _scrollBar->SetScrollbar( _scrollBar->GetThumbPosition(), GetDisplayableItemCount(), _itemaccessor->Size(), 5, true); UpdateItemPanelList(); event.Skip(); } // スクロール void MyObjectListWidget::OnScroll(wxScrollEvent& event) { UpdateItemPanelList(); event.Skip(); }
#pragma once #include <wx/wx.h> // 自作リストコントロールで一つのアイテムを表示するためのパネル class ItemPanel :public wxPanel { private: // このパネルが表示上で上から何番目かを表す int panelIndex = 0; class MyObjectListWidget* m_basepanel; size_t _item_index = 0; // このアイテムが指し示すアイテムのインデックス public: ItemPanel(wxWindow* parent); //! @brief 自作リストコントロールへのポインタを取得 class MyObjectListWidget* GetBasePanel() { return m_basepanel; } //! @brief このアイテムのインデックスを取得 size_t GetItemIndex()const { return _item_index; } //! @brief このパネルが表示上で上から何番目かを取得 int GetPanelIndex()const { return panelIndex; } //! @brief このアイテムパネルに各種情報をセット void SetPanelInfo(class MyObjectListWidget* base, int panelindex, size_t itemindex); //! @brief このパネルの表示内容を更新 void UpdatePanel(); /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// virtual size_t Size()const = 0; //! @brief itemindex番のアイテムが表示可能か、つまりインデクスの範囲内かをチェック //! @param itemindex アイテムのインデックス //! @return true: 表示可能 false: 表示不可 virtual bool IsValidItem(const size_t itemindex) = 0; //! @brief パネル一枚の表示を更新 //! @param panelIndex パネルのインデックス //! @param itemindex アイテムのインデックス //! @details スクロールが発生するたびにパネル内のウィジェットの表示内容を //! itemindex番の内容に更新する virtual void UpdatePanelItem(int panelIndex, size_t itemindex) = 0; //! @brief パネル一枚の表示を更新(インデクス範囲外の場合) //! @param panelIndex パネルのインデックス //! @details スクロールが発生して、パネルの表示内容を更新した際に、 //! itemindex番の内容が存在しない場合に呼び出される virtual void UpdatePanelOutOfRange(int panelIndex) = 0; //! @brief パネル一枚の再描画の内容を記述する //! @param dc 描画コンテキスト //! @param panelIndex パネルのインデックス //! @param itemindex アイテムのインデックス //! @details パネルのOnPaintイベント発生時に呼び出される。 //! 背景色などを変える場合はここに記載 virtual void PaintValid(wxDC& dc, int panelIndex, size_t itemindex) = 0; //! @brief パネル一枚の再描画の内容を記述する(インデクス範囲外の場合) //! @param dc 描画コンテキスト //! @param panelIndex パネルのインデックス //! @details パネルのOnPaintイベント発生時に呼び出される。 //! 背景色などを変える場合はここに記載 //! 特に表示内容がない場合は、背景色を変えるなどして、パネルが無いように見せる virtual void PaintOutOfRange(wxDC& dc, int panelIndex) = 0; void OnPaint(wxPaintEvent& event); //! @brief パネルをクリックしたときの挙動。 //! 有効なパネル(表示可能なアイテム)をクリックした場合は、OnEventLeftClickを呼び出す) //! 無効なパネル(表示不可能なアイテム)をクリックした場合は、OnEventLeftClickOutOfRangeを呼び出す virtual void OnLeftClick(wxMouseEvent& event); //! @brief 有効なパネルをクリックしたときの挙動 virtual void OnEventLeftClick(wxMouseEvent& event, int panelIndex, size_t itemindex); //! @brief 無効なパネルをクリックしたときの挙動 virtual void OnEventLeftClickOutOfRange(wxMouseEvent& event, int panelIndex); }; //! @brief パネルとパネルに表示するデータを関連付けるためのクラス //! @details パネルの生成時、各パネルはデータへアクセスできなければならない。 //! CreatePanel関数でパネルを作成するときに、データのポインタなどを用いて各パネルがデータへアクセスできるようにする. class MyObjectListItemAccessor { public: //! @brief パネルを生成する virtual ItemPanel* CreatePanel(wxWindow* parent) = 0; virtual ~MyObjectListItemAccessor() {}; //! @brief データの要素数を取得 virtual size_t Size()const = 0; };
#include "MyItemPanel.hpp" #include "MyObjectListWidget.hpp" ItemPanel::ItemPanel(wxWindow* parent) : m_basepanel(nullptr), wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxFULL_REPAINT_ON_RESIZE, wxPanelNameStr) { Bind(wxEVT_PAINT, &ItemPanel::OnPaint, this); Bind(wxEVT_LEFT_DOWN, &ItemPanel::OnLeftClick, this); } void ItemPanel::SetPanelInfo(class MyObjectListWidget* base, int panelindex, size_t itemindex) { panelIndex = panelindex; _item_index = itemindex; m_basepanel = base; } void ItemPanel::UpdatePanel() { if (IsValidItem(_item_index) == true) { UpdatePanelItem(panelIndex, _item_index); } else { UpdatePanelOutOfRange(panelIndex); } } void ItemPanel::OnPaint(wxPaintEvent& event) { ItemPanel* panel = (ItemPanel*)event.GetEventObject(); MyObjectListWidget* parent = panel->GetBasePanel(); wxPaintDC dc(panel); if (IsValidItem(panel->GetItemIndex()) == true) { PaintValid( dc, panel->GetPanelIndex(), panel->GetItemIndex() ); } else { PaintOutOfRange( dc, panel->GetPanelIndex() ); } } void ItemPanel::OnLeftClick(wxMouseEvent& event) { MyObjectListWidget* parent = this->GetBasePanel(); if (parent) { if (IsValidItem(this->GetItemIndex()) == false) { OnEventLeftClickOutOfRange( event, this->GetPanelIndex() ); } else { OnEventLeftClick( event, this->GetPanelIndex(), this->GetItemIndex() ); parent->FitPosition(this->GetPanelIndex()); } } } void ItemPanel::OnEventLeftClick(wxMouseEvent& event, int panelIndex, size_t itemindex) { MyObjectListWidget* parent = this->GetBasePanel(); parent->SetActiveItemIndex(itemindex); } void ItemPanel::OnEventLeftClickOutOfRange(wxMouseEvent& event, int panelIndex) { }
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include"MyObjectListWidget.hpp" ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <string> // データ struct MyData { std::string name; int index; };
//! @brief リストの各項目を表示するパネル class MyItemPanel :public ItemPanel { std::weak_ptr<std::vector<MyData>> m_pdata; wxStaticText* _label; public: MyItemPanel(wxWindow* parent) : ItemPanel(parent){ _label = new wxStaticText(this, wxID_ANY, "<unknown>"); _label->SetPosition(wxPoint(10, 10)); // ラベルの背景色とテキスト色を設定 _label->SetBackgroundColour(wxColour(255, 255, 255)); // 白色の背景 _label->SetForegroundColour(wxColour(0, 0, 0)); // 黒色のテキスト _label->Show(); } void setData(std::weak_ptr<std::vector<MyData>> data) { m_pdata = data; } virtual size_t Size()const override { return m_pdata.lock()->size(); }; virtual bool IsValidItem(const size_t itemindex)override { auto datalist = m_pdata.lock(); if (itemindex < datalist->size()) return true; return false; } virtual void UpdatePanelItem(int panelIndex, size_t itemindex)override { auto data = m_pdata.lock(); if (_label) { _label->SetLabel( wxString::Format( "%d Item %s [%d]", panelIndex, (*data)[itemindex].name.c_str(), (*data)[itemindex].index ) ); } // パネルの再描画 Refresh(); }; virtual void UpdatePanelOutOfRange(int panelIndex)override { auto data = m_pdata.lock(); if (_label) { _label->SetLabel("out of range"); } // パネルの再描画 Refresh(); } virtual void PaintValid(wxDC& dc, int panelIndex, size_t itemindex)override { dc.SetPen(wxPen(wxColour(0, 0, 0), 1)); if (itemindex == this->GetBasePanel()->GetActiveItemIndex()) { dc.SetBrush(wxBrush(wxColour(255, 0, 0))); } else { dc.SetBrush(wxBrush(wxColour(200, 200, 255))); } wxSize size = GetSize(); dc.DrawRectangle(0, 0, size.x, size.y); } virtual void PaintOutOfRange(wxDC& dc, int panelIndex)override { dc.SetPen(wxPen(wxColour(255, 0, 200), 2)); dc.SetBrush(wxBrush(wxColour(255, 0, 200))); wxSize size = GetSize(); dc.DrawRectangle(0, 0, size.x, size.y); } };
//! @brief リストの各項目を生成するクラス class MyOLAccessor :public MyObjectListItemAccessor { std::weak_ptr<std::vector<MyData>> m_data; public: MyOLAccessor(std::weak_ptr<std::vector<MyData>> data) : m_data(data) {} virtual ItemPanel* CreatePanel(wxWindow* parent)override { auto ptr = new MyItemPanel(parent); ptr->setData(m_data); return ptr; } virtual size_t Size()const override{ return m_data.lock()->size(); } };
// ウィンドウ作成 class MyFrame : public wxFrame { std::shared_ptr<std::vector<MyData>> m_data; public: void PostCreate() { m_data = std::make_shared<std::vector<MyData>>(); for(size_t i = 0; i < 13; i++) { m_data->push_back({ "name " + std::to_string(i), (int)i }); } MyObjectListWidget* objList = new MyObjectListWidget( this, std::make_shared<MyOLAccessor>(m_data), // データへアクセス 80 // アイテムの高さ ); objList->SetSize(0, 0, 200, 200); this->Layout(); // レイアウトの更新 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
langchain-ollamaがconda対応していない。どうせpipするので全てpipで導入する。
faissを使い、テキストをベクトル化することでテキストデータの距離を検索できるようにする。
data.txtをベクトル化し、faiss_indexという名前でファイル保存する。
from langchain_ollama import OllamaEmbeddings from langchain_community.vectorstores import FAISS from langchain.schema import Document import re output_data_name = "faiss_index" # pip install faiss-cpu # pip install -U langchain-ollama # pip install langchain-community ########################################################### # data.txtを1行ずつ読み込み、1行=1チャンクとして扱う (1チャンク=1ベクトル) documents = [] with open("data.txt", encoding="utf-8") as f: for line in f: line = line.strip() if line: documents.append(Document(page_content=line)) ########################################################### # データを表示 for i, doc in enumerate(documents): print(f"チャンク{i}: {doc.page_content}") ########################################################### # ステップ3: ベクトル化してFAISSに保存 embedding = OllamaEmbeddings(model="nomic-embed-text") # テキスト用埋め込みモデル vectorstore = FAISS.from_documents(documents, embedding) vectorstore.save_local(output_data_name)
from langchain_ollama import OllamaEmbeddings from langchain_community.vectorstores import FAISS from langchain_ollama import OllamaLLM from langchain.chains import RetrievalQA import re usellm = OllamaLLM(model="qwen3:8b") ########################################################### embedding = OllamaEmbeddings(model="nomic-embed-text") # テキスト埋め込み用モデル retriever = FAISS.load_local( "faiss_index" ,embedding # 検索用モデル ,allow_dangerous_deserialization=True ).as_retriever() qa_chain = RetrievalQA.from_chain_type(llm=usellm, retriever=retriever) ########################################################### # 質問する query = "解説文から、関西地方に関係する物語であることがわかる作品を教えてください" answer = qa_chain.invoke(query) raw = answer["result"] response_without_think = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL) print(response_without_think)
文字列をスクリプト単位に分離する。スクリプトはHarfBuzzで描画するときに使う。
#include <iostream> #include <unicode/ubrk.h> #include <unicode/ustring.h> #include <unicode/uscript.h> #include <unicode/uchar.h> #include <vector> #include <fstream> #include <algorithm> #include <Windows.h> #include <fcntl.h> #include <io.h> // 要リンク #if defined(_DEBUG) #pragma comment(lib, "icuucd.lib") #else #pragma comment(lib, "icuuc.lib") #endif struct Grapheme { int32_t start; int32_t end; };
// 文字列を書記素単位でアクセスできるようにするリストを作成 std::vector<Grapheme> createGraphemeList(const char16_t* text, const size_t length) { UErrorCode status = U_ZERO_ERROR; std::vector<Grapheme> graphemes; // イテレータ作成 UBreakIterator* bi = ubrk_open(UBRK_CHARACTER, "ja_JP", nullptr, 0, &status); if (U_FAILURE(status)) { return std::vector<Grapheme>(); // エラーが発生 } // テキストを設定 ubrk_setText(bi, (const UChar*)text, length, &status); if (U_FAILURE(status)) { ubrk_close(bi); return std::vector<Grapheme>(); // エラーが発生 } // 最初の書記素の位置を取得 int32_t start = ubrk_first(bi); int32_t end; // 書記素リストを作成 while ((end = ubrk_next(bi)) != UBRK_DONE) { graphemes.push_back(Grapheme{ start, end }); start = end; } // 終了処理 ubrk_close(bi); return graphemes; }
// 指定した書記素をutf16 からコードポイントに変換 UChar32 getCodepoint(const char16_t* text, const Grapheme& g) { UChar32 codepoint; size_t start = g.start; size_t end = g.end; U16_NEXT(text, start, end, codepoint); return codepoint; }
// 各文字ごとにスクリプトを特定 std::vector< std::pair<size_t, UScriptCode> > createScriptList(const char16_t* text, const std::vector<Grapheme>& glist) { std::vector< std::pair<size_t, UScriptCode> > scliptslice; UScriptCode latest; UErrorCode err; UChar32 codepoint; UScriptCode script; for (size_t i = 0; i < glist.size(); i++) { codepoint = getCodepoint(text, glist[i]); script = uscript_getScript(codepoint, &err); if (U_SUCCESS(err)) { latest = script; } else { latest = USCRIPT_UNKNOWN; } scliptslice.push_back({ i, latest }); } return scliptslice; }
struct ScriptSlice { size_t grapheme_start; size_t grapheme_end; UScriptCode script; };
// 一文字ごとに設定されているスクリプトを元に、同じスクリプトが連続している部分をひとまとめにする std::vector<ScriptSlice> ScriptListShrink(const std::vector< std::pair<size_t, UScriptCode> >& ss) { std::vector<ScriptSlice> slist; UScriptCode script = ss[0].second; slist.push_back({ ss[0].first, ss[0].first, ss[0].second}); for (size_t i = 1; i < ss.size(); i++) { if (ss[i].second != slist.back().script) { slist.back().grapheme_end = ss[i].first-1; slist.push_back({ ss[i].first,ss[i].first, ss[i].second }); } } slist.back().grapheme_end = ss.back().first; return slist; }
int main() { // 日本語ロケール std::locale::global(std::locale("japanese")); std::u16string u16str = u"あいうイロハホヘト你好😁👩👨👦👧ÄɪʊabcQué"; // 書記素リスト作成 std::vector<Grapheme> glist = createGraphemeList(u16str.data(), u16str.length()); // 書記素リストを元にスクリプトリストを作成 std::vector< std::pair<size_t, UScriptCode> > slist = createScriptList(u16str.data(), glist); // スクリプトリストを元に、同じスクリプトが連続している部分をひとまとめにする std::vector<ScriptSlice> ss = ScriptListShrink(slist); // 同じスクリプト単位で表示 for (size_t i = 0; i < ss.size(); i++) { size_t u16start = glist[ss[i].grapheme_start].start; size_t u16end = glist[ss[i].grapheme_end].end; size_t length = u16end - u16start; std::u16string u16wstr(u16str.data() + u16start, length); std::wcout << L"[" << ss[i].script << L"] "; WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), u16wstr.c_str(), length, nullptr, nullptr); WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"\n", 2, nullptr, nullptr); } return 0; }
、や全角英数など文字によってはイメージと違うことがある。
std::u16string u16str = u"あいうイロハホヘト你好😁👩👨👦👧ÄɪʊabcQué,'\"-、ABC”‘’"
vtkPolyData一つだけであればvtp形式で保存できるが、複数のvtkPolyDataを保存する場合はvtm形式にする。vtmは一つの.vtmと、各vtkPolyData毎に.vtpを出力する。
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkActor.h> #include <vtkPolyData.h> #include <vtkCellArray.h> #include <vtkPoints.h> #include <vtkLine.h> #include <vtkTriangle.h> #include <vtkPolyDataMapper.h> //properties #include <vtkProperty.h> #include <vtkPolyLine.h> #include <vtkCylinderSource.h> #include <vtkXMLPolyDataWriter.h> #include <vtkXMLMultiBlockDataWriter.h> #include <vtkMultiBlockDataSet.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle);
vtkSmartPointer<vtkPolyData> PolyLine() { // ポイントデータの作成 vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New(); double rrr = 1.0; points->InsertNextPoint(0.0, 0.0, 0.0); points->InsertNextPoint(-rrr, 0.0, 0.0); points->InsertNextPoint(-rrr, 0.0, rrr); points->InsertNextPoint(0.0, 0.0, rrr); points->InsertNextPoint(0.0, 0.0, 0.0); // 閉じた形状 // PolyLineの作成 vtkSmartPointer<vtkPolyLine> polyLine = vtkSmartPointer<vtkPolyLine>::New(); polyLine->GetPointIds()->SetNumberOfIds(5); // 点の数 for (vtkIdType i = 0; i < 5; ++i) { polyLine->GetPointIds()->SetId(i, i); } // CellArrayに追加 vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New(); cells->InsertNextCell(polyLine); // PolyDataの作成 vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(points); polyData->SetLines(cells); return polyData; }
vtkSmartPointer<vtkPolyData> PolyCylinder() { // vtkCylinderSource の作成 vtkSmartPointer<vtkCylinderSource> cylinderSource = vtkSmartPointer<vtkCylinderSource>::New(); cylinderSource->SetHeight(5.0); cylinderSource->SetRadius(0.2); cylinderSource->SetResolution(50); cylinderSource->Update(); // 出力を確定させる // vtkPolyData に変換 vtkSmartPointer<vtkPolyData> cylinderPolyData = cylinderSource->GetOutput(); return cylinderPolyData; }
void save_as_vtm(std::vector<vtkSmartPointer<vtkPolyData>> polyDataList, const std::string& filename) { vtkSmartPointer<vtkXMLMultiBlockDataWriter> writer = vtkSmartPointer<vtkXMLMultiBlockDataWriter>::New(); writer->SetFileName(filename.c_str()); vtkSmartPointer<vtkMultiBlockDataSet> multiBlock = vtkSmartPointer<vtkMultiBlockDataSet>::New(); for (size_t i = 0; i < polyDataList.size(); ++i) { multiBlock->SetBlock(i, polyDataList[i]); } writer->SetInputData(multiBlock); writer->Write(); }
int main(int /*argc*/, char** /*argv*/) { vtkSmartPointer<vtkPolyData> polyline = PolyLine(); vtkSmartPointer<vtkPolyData> cylinder = PolyCylinder(); save_as_vtm({ polyline, cylinder }, "items.vtm"); vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->SetBackground(0.1, 0.1, 0.1); vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(polyline); // Actorの作成 vtkSmartPointer<vtkActor> actorPolyline = vtkSmartPointer<vtkActor>::New(); actorPolyline->SetMapper(mapper); vtkSmartPointer<vtkPolyDataMapper> cylinderMapper = vtkSmartPointer<vtkPolyDataMapper>::New(); cylinderMapper->SetInputData(cylinder); vtkSmartPointer<vtkActor> cylinderActor = vtkSmartPointer<vtkActor>::New(); cylinderActor->SetMapper(cylinderMapper); renderer->AddActor(actorPolyline); renderer->AddActor(cylinderActor); vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); interactor->SetRenderWindow(renderWindow); renderWindow->Render(); interactor->Start(); return 0; }
実行すると、
の3ファイルが出力される。
動作確認はparaviewでできる。
Skiaでフォントを指定して文字列を描画する。Freetype2とHarfbuzzが必要。
Skiaでフォント使用(1)文字列描画
#pragma comment(lib,"skia.dll.lib") #pragma comment(lib,"skshaper.dll.lib") ///////////////////////////////////// #include "skia/include/core/SkCanvas.h" #include "skia/include/core/SkBitmap.h" #include "skia/include/core/SkSurface.h" #include "include/core/SkSurface.h" ///////////////////////////////////// // テキスト描画 #include "include/core/SkTypeface.h" #include "include/core/SkFont.h" #include "include/core/SkFontMgr.h" #include "include/ports/SkFontMgr_directory.h" // フォントマネージャ作成 #include "include/core/SkFontMetrics.h" // 文字のレイアウト #include "modules/skshaper/include/SkShaper.h" ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // Png保存に必要 #include "include/encode/SkPngEncoder.h" #include "include/core/SkStream.h" /////////////////////////////////////
// SkShaperで文字列を描画するためのクラス class CanvasRunHandler : public SkShaper::RunHandler { public: CanvasRunHandler(SkCanvas* canvas, SkPoint origin, const SkFont& font, const SkPaint& paint) : canvas_(canvas), origin_(origin), font_(font), paint_(paint) {} void runInfo(const RunInfo& info) override { glyphCount_ = info.glyphCount; glyphs_.resize(glyphCount_); positions_.resize(glyphCount_); } Buffer runBuffer(const RunInfo& info) override { return { glyphs_.data(), // glyphs positions_.data(), // positions nullptr, // offsets nullptr, // clusters origin_ // point offset to add to all positions }; } void commitRunBuffer(const RunInfo& info) override { canvas_->drawGlyphs( glyphCount_, glyphs_.data(), positions_.data(), runOrigin_, font_, paint_ ); } void beginLine() override { } void commitRunInfo() override { } void commitLine() override { } private: SkCanvas* canvas_; SkPoint origin_; SkPoint runOrigin_; SkFont font_; SkPaint paint_; int glyphCount_ = 0; SkVector runOffset_; std::vector<SkGlyphID> glyphs_; std::vector<SkPoint> positions_; };
// テキスト描画のテスト SkBitmap TextTest() { // フォントマネージャ作成 sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_Custom_Directory("C:\\Windows\\Fonts\\"); // フォントが格納されているディレクトリ指定 //auto typeface = fontMgr->matchFamilyStyle("Segoe UI Emoji", SkFontStyle::Normal()); // カラー絵文字はうまく描画できない auto typeface = fontMgr->matchFamilyStyle("MS Gothic", SkFontStyle::Normal()); // フォントの取得 /////////////////////////////////////////////////////////////////////////////////////////// SkImageInfo imginfo = SkImageInfo::Make(500, 100, kN32_SkColorType, kPremul_SkAlphaType); sk_sp<SkSurface> surface = SkSurfaces::Raster(imginfo); SkCanvas* canvas = surface->getCanvas(); canvas->clear(SK_ColorWHITE); // 背景を白にクリア /////////////////////////////////////////////////////////////////////////////////////////// // テキストの描画 SkPaint paint; paint.setColor(SK_ColorBLACK); // テキストの色 paint.setAntiAlias(false); // アンチエイリアスを有効にする /////////////////////////////////////////////////////////////////////////////////////////// // フォントの設定 SkFont font; font.setEdging(SkFont::Edging::kAntiAlias); // エッジングの設定 font.setSize(50.f * 72 / 96); // テキストのサイズ font.setTypeface(typeface); // フォントの設定 /////////////////////////////////////////////////////////////////////////////////////////// SkScalar x = 30.f; // テキストの位置(横)を設定 SkScalar y = 50.0f; // テキストの位置(縦)を設定 SkFontMetrics metrics; font.getMetrics(&metrics); y -= metrics.fAscent;// テキストの位置(高さ)を設定 /////////////////////////////////////////////////////////////////////////////////////////// // カラー絵文字はうまく描画できない //SkString text((const char*)u8"😊👩👨👦👧"); SkString text((const char*)u8"がぎぐげご"); /////////////////////////////////////////////////////////////////////////////////////////// #if 1 // テキストの描画(結合文字対応) int width = 500; std::unique_ptr<SkShaper> shaper = SkShaper::Make(); SkPoint origin = SkPoint::Make(x, y); CanvasRunHandler runHandler(canvas, origin, font, paint); shaper->shape(text.data(), strlen(text.c_str()), font, true, width, &runHandler); #else // テキストの描画 canvas->drawString(text, x, y, font, paint); #endif ////////////////////////////////////////////////// // // PNG出力 SkPixmap pixmap; if (surface->peekPixels(&pixmap)) { SkFILEWStream stream("output.png"); SkPngEncoder::Options options; options.fZLibLevel = 9; SkPngEncoder::Encode(&stream, pixmap, options); } // Bitmap化 SkBitmap bitmap; bitmap.allocPixels(imginfo); surface->readPixels(bitmap.pixmap(), 0, 0); return bitmap; }
カラー絵文字についてもフォントをSegoe UI Emojiなどにすれば一応動くのだが、デバッグモードでは正しく出力できるのにリリースモードでは透明度かなにかがおかしくなる。意味が分からない。
画像ビューア的なものを作っているのだが、2000件くらいの画像を一括読み込みすると読み込みまでフリーズするような状態になったので、スレッドプールを使って操作中に読み込みをするようにした。
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include <string> #include <wx/statbmp.h> #include <vector> #include <memory> #include <filesystem> #include "ThreadWorker.hpp"
//! @brief データをスレッドで読み込むクラス //! @details データの読み込みをスレッドプールで行う //! @tparam Source データの入手元 std::filesystem::pathなど //! @tparam Reference データの格納先 std::shared_ptr<DATA> など //! @tparam Job データの読み込みを行うスレッドプールのジョブ型 template<typename Source, typename Reference, typename Job> class MultiDataLoader { std::unique_ptr<ThreadPool> _pool; // スレッドプール public: MultiDataLoader(const int _threadcount_) : _pool( new ThreadPool(_threadcount_) ) {} bool isFinished() { return _pool->isAllJobCompleted(); } ~MultiDataLoader() { _pool.reset(); } //! @brief データの読み込みを開始する //! @param _source_ データの入手元一覧 //! @param ploaded データの格納先 //! @param unloadedsign 未読み込みの場合の値 nullptrなど void load(const std::vector<Source>& _source_, std::vector<Reference>* _ploaded_, Reference _unloadedsign_=nullptr) { // データの格納先を用意 _ploaded_->clear(); _ploaded_->resize(_source_.size(), _unloadedsign_); for (size_t index = 0; index < _source_.size(); index++) { _pool->add(std::make_unique<Job>(_ploaded_, _source_[index], index)); } } };
//! @brief データの読み込みを行うジョブ class JobLoadData : public JobBase { using Reference = std::shared_ptr<wxBitmap>; std::filesystem::path path; size_t index; std::vector<Reference>* ref; public: //! @brief コンストラクタ //! @param _pref_ データの格納先 //! @param _path_ データの入手元 //! @param _index_ データの格納先のインデックス JobLoadData(std::vector<Reference>* _pref_, const std::filesystem::path& _path_, size_t _index_) { ref = _pref_; path = _path_; index = _index_; } virtual void run() override { wxString fname = wxString::FromUTF8((const char*)path.u8string().c_str()); wxLogNull logNo; // 警告を抑制 // 拡張子チェック (*ref)[index] = std::make_shared<wxBitmap>(fname, wxBITMAP_TYPE_ANY); } };
//! @brief 指定したディレクトリ以下の、指定した拡張子のファイル一覧を取得する //! @param _dir_path_ 検索するディレクトリのパス //! @param extensions 検索対象の拡張子 //! @return 検索結果のファイルパス一覧 std::vector<std::filesystem::path> get_files_with_extensions(const std::filesystem::path& _dir_path_, const std::vector<std::string>& _extensions_) { std::vector<std::filesystem::path> file_list; if (!std::filesystem::exists(_dir_path_) || !std::filesystem::is_directory(_dir_path_)) { std::cerr << "Error: The provided path is not a valid directory.\n"; return file_list; } for (const auto& entry : std::filesystem::recursive_directory_iterator(_dir_path_)) { if (entry.is_regular_file()) { std::string ext = entry.path().extension().string(); if (std::find(_extensions_.begin(), _extensions_.end(), ext) != _extensions_.end()) { file_list.push_back(entry.path()); } } } return file_list; }
class MyFrame : public wxFrame { using DataLoader = MultiDataLoader<std::filesystem::path, std::shared_ptr<wxBitmap>, JobLoadData>; std::unique_ptr< DataLoader > ploader; public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size), currentIndex(0) { // 画像ファイルの読み込み // wxBITMAP_TYPE_JPEG 等 wxInitAllImageHandlers(); // ファイル一覧の取得 auto sourcepath = LR"(C:\test\images)"; std::vector<std::filesystem::path> paths = get_files_with_extensions(sourcepath, { ".png",".jpg"}); // データ読み込みの準備(8スレッド) ploader = std::make_unique< DataLoader >(8); // データのロード開始 ploader->load(paths, &bitmaps); // 初期状態 空の画像を表示 imageCtrl = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxPoint(10, 10), wxSize(400, 300)); // キーボードイベントを処理 Bind(wxEVT_KEY_DOWN, &MyFrame::OnKeyDown, this); } ~MyFrame() { ploader.reset(); } private: std::vector<std::shared_ptr<wxBitmap>> bitmaps; wxStaticBitmap* imageCtrl; size_t currentIndex; void OnKeyDown(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_LEFT) { if (currentIndex > 0) { currentIndex--; UpdateImage(); } } else if (event.GetKeyCode() == WXK_RIGHT) { if (currentIndex < bitmaps.size() - 1) { currentIndex++; UpdateImage(); } } } void UpdateImage() { if(bitmaps[currentIndex] == nullptr){ //imageCtrlに表示されている画像を削除 imageCtrl->SetBitmap(wxNullBitmap); } else { imageCtrl->SetBitmap(*bitmaps[currentIndex]); } Layout(); // レイアウトを更新 } };
class MyApp : public wxApp { public: virtual bool OnInit() { // windowsのコンソールでUTF-8を表示するために必要 // ロケール設定 std::locale::global(std::locale("ja_JP.UTF-8")); std::cerr.imbue(std::locale("ja_JP.UTF-8")); std::cout.imbue(std::locale("ja_JP.UTF-8")); SetConsoleOutputCP(CP_UTF8); MyFrame* frame = new MyFrame("Bitmap Viewer", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; wxIMPLEMENT_APP(MyApp);
画像フォルダ内のデータが読み込まれるので、左右キーで表示切替ができる。
スレッドプールのコードで、スレッドプールそのものを破棄するときに、未処理のキューをすべて削除して即終了できるように修正。
#pragma once #include <deque> #include <vector> #include <thread> #include <mutex> #include <condition_variable> #include <cassert> //! @brief スレッドに行わせる仕事のインターフェース //! @details run()メソッドを実装し、処理内容を記述する \n //! 使用例: //! @code //! class MyWork : public JobBase { //! public: //! void run() override {} //! }; //! @endcode class JobBase { public: virtual void run() = 0; virtual ~JobBase() {} }; //! @brief ジョブをためるキュー class Queue { public: Queue() {} void push(std::unique_ptr<JobBase>&& data) { _deque.emplace_back(std::move(data)); } std::unique_ptr<JobBase> pop() { if (_deque.empty()) { return nullptr; } auto data = std::move(_deque.front()); _deque.pop_front(); return data; } bool empty() const { return _deque.empty(); } void clear() { _deque.clear(); // 未処理のジョブを全削除 } private: // ジョブがたまっているキュー // ThreadPoolで作成したN個のThreadWorkerスレッドが、このキューからジョブを取り出して実行する std::deque<std::unique_ptr<JobBase>> _deque; }; //! スレッドプールで動いているスレッドの実装 class ThreadWorker { public: ThreadWorker(bool& isTerminationRequested, Queue& queue, std::mutex& mutex, std::condition_variable& cv, int& activeJobCount, std::condition_variable& activeJobCv) : _isTerminationRequested(isTerminationRequested), _queue(queue), _mutex(mutex), _cv(cv), _activeJobCount(activeJobCount), _activeJobCv(activeJobCv) {} void operator()() { while (1) { std::unique_ptr<JobBase> jobinstance; { std::unique_lock<std::mutex> ul(_mutex); while (_queue.empty()) { if (_isTerminationRequested) { return; } _cv.wait(ul); } jobinstance = _queue.pop(); assert(jobinstance != nullptr); _activeJobCount++; } jobinstance->run(); // ジョブを実行 { std::unique_lock<std::mutex> ul(_mutex); _activeJobCount--; // 実行中ジョブ数を減らす if (_activeJobCount == 0 && _queue.empty()) { _activeJobCv.notify_all(); // すべてのジョブが完了したことを通知 } } } } private: bool& _isTerminationRequested; Queue& _queue; std::mutex& _mutex; std::condition_variable& _cv; int& _activeJobCount; // 実行中ジョブ数 std::condition_variable& _activeJobCv; // waitForCompletion() へ通知 }; //! @brief スレッドプールクラス //! @details N個のスレッドで、M個のジョブを実行する。\n //! waitForCompletion()で、現在キューを入れたすべてのジョブが完了するまで待機できる \n //! 使用例: //! @code //! ThreadPool pool(4); //! pool.add(std::make_unique<MyWork>()); //! pool.add(std::make_unique<MyWork>()); //! pool.waitForCompletion(); //! pool.add(std::make_unique<MyWork>()); //! pool.add(std::make_unique<MyWork>()); //! @endcode class ThreadPool { public: ThreadPool(int threadCount) : _isTerminationRequested(false), _activeJobCount(0) { for (int n = 0; n < threadCount; n++) { auto worker = std::make_shared<ThreadWorker>(_isTerminationRequested, _queue, _mutex, _cv, _activeJobCount, _activeJobCv); _workers.push_back(worker); _threads.emplace_back(std::thread(std::ref(*worker))); } } ~ThreadPool() { { std::unique_lock<std::mutex> ul(_mutex); _isTerminationRequested = true; _queue.clear(); // 未処理のジョブを全削除し中断 } _cv.notify_all(); for (auto& thread : _threads) { if (thread.joinable()) { thread.join(); // スレッドを安全に終了 } } } //! @brief すべてのジョブが完了するまで待機する void waitForCompletion() { std::unique_lock<std::mutex> ul(_mutex); _activeJobCv.wait(ul, [this]() { return _activeJobCount == 0 && _queue.empty(); }); } //! @brief すべてのジョブが完了したかを確認する関数 bool isAllJobCompleted() { std::unique_lock<std::mutex> ul(_mutex); return _activeJobCount == 0 && _queue.empty(); } //! @brief ジョブをキューに追加 //! @param jobinstance ジョブ void add(std::unique_ptr<JobBase>&& jobinstance) { { std::unique_lock<std::mutex> ul(_mutex); _queue.push(std::move(jobinstance)); } _cv.notify_all(); } private: bool _isTerminationRequested; Queue _queue; std::mutex _mutex; std::condition_variable _cv; std::vector<std::thread> _threads; std::vector<std::shared_ptr<ThreadWorker>> _workers; int _activeJobCount; // 実行中ジョブ数 std::condition_variable _activeJobCv; // ジョブの完了を待機するための条件変数 };