[imgui] 5. Paint 구현

[imgui] 5. Paint 구현

2023년 7월 8일
imgui

목표 #

  • 그림판을 만들기
  • 색 변경, 펜 두께 변경, Save, Load, CLear 기능 Paint

구현코드 #

메인함수 #

 1#include <cstdint>
 2#include <string_view>
 3#include <imgui.h>
 4#include <vector>
 5
 6class WindowClass
 7{
 8public:
 9    // position, color, size 3가지 데이터를 묶어서 하나의 PointData로 사용
10    using PointData = std::tuple<ImVec2, ImColor, float>;
11
12    static constexpr auto popUpFlags =
13        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
14        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
15    static constexpr auto popUpSize = ImVec2(300.0f, 100.0f);
16    static constexpr auto popUpButtonSize = ImVec2(120.0f, 0.0f);
17    static constexpr auto popUpPos = ImVec2(1280.0f / 2.0f - popUpSize.x / 2.0f,
18                                            720.0f / 2.0f - popUpSize.y / 2.0f);
19
20public:
21    WindowClass()
22        : points({}), canvasPos({}), currentDrawColor(ImColor(255, 255, 255)),
23          pointDrawSize(2.0F), filenameBuffer("test.bin"){};
24
25    void Draw(std::string_view label);
26
27private:
28    void DrawMenu();          // 색선택, Size선택, Save, Load 버튼등
29    void DrawCanvas();        // 캔버스 그리기
30    void DrawColorButtons();  // 색션택 버튼을 모듈화
31    void DrawSizeSettings();  // 사이즈 선택을 모듈화
32    void ClearCanvas();       // 캔버스 클리어
33
34    // 바이너리로 PointData 저장 및 복원
35    void SaveToImageFile(std::string_view filename);
36    void LoadFromImageFile(std::string_view filename);
37
38    // 이전에 TextEditor에서 사용한팝업
39    void DrawSavePopup();
40    void DrawLoadPopup();
41
42private:
43    // 캔버스 가로 세로 길이, 컬러채널
44    std::uint32_t numRows = 800;    // Row's size;
45    std::uint32_t numCols = 600;    // Column's size;
46    std::uint32_t numChannels = 3;
47
48    ImVec2 canvasPos;
49    ImVec2 canvasSize = ImVec2(static_cast<float>(numRows), static_cast<float>(numCols));
50
51    std::vector<PointData> points;  // 실제 그림 데이터가 저장되는곳
52    ImColor currentDrawColor;       // 선택된 Color
53    float pointDrawSize;            // point(Dot) Size;
54
55    char filenameBuffer[256]; 
56};

void WindowClass::Draw(std::string_view label) { constexpr static auto window_flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar; constexpr static auto window_size = ImVec2(1280.0F, 720.0F); constexpr static auto window_pos = ImVec2(0.0F, 0.0F);

ImGui::SetNextWindowSize(window_size);
ImGui::SetNextWindowPos(window_pos);

ImGui::Begin(label.data(), nullptr, window_flags);

DrawMenu();
DrawCanvas();

ImGui::End();

}


DrawMenu #

버튼을 만들고, save, load 버튼을 눌렀을때 팝업 띄우는건 TextEditor와 중복된다.
DrawMenu 코드 내부에서 ClearCanvas, DrawColorButtons, DrawSizeSettings 를 호출해서 그린다.


DrawColorButtons / DrawSizeSettings #

Paint

 1void WindowClass::DrawColorButtons()
 2{
 3    const auto selected_red = currentDrawColor == ImColor(255, 0, 0);
 4    const auto selected_green = currentDrawColor == ImColor(0, 255, 0);
 5    const auto selected_blue = currentDrawColor == ImColor(0, 0, 255);
 6    const auto selected_white = currentDrawColor == ImColor(255, 255, 255);
 7    const auto none_preset_color =
 8        !selected_red && !selected_green && !selected_blue && !selected_white;
 9
10    constexpr static auto orange = ImVec4(1.0F, 0.5F, 0.0F, 1.0F);
11
12    // 만약 선택됐다면, 버튼 색상을 orange로 변경
13    if (selected_red)
14        ImGui::PushStyleColor(ImGuiCol_Button, orange);
15    if (ImGui::Button("Red"))
16        currentDrawColor = ImColor(255, 0, 0);
17    if (selected_red)
18        ImGui::PopStyleColor();
19    // 나갈땐 스택에서 pop 해줘야 다른버튼에 영향을 주지 않는다. 
20
21    ImGui::SameLine();
22
23    /* ... selected_green, selected_blue, selected_white 중복코드 생략 ... */
24
25    if (none_preset_color)
26        ImGui::PushStyleColor(ImGuiCol_Button, orange);
27    if (ImGui::Button("Choose"))
28        ImGui::OpenPopup("Color Picker");
29    // 일반 Popup으로 호출
30    if (ImGui::BeginPopup("Color Picker"))
31    {
32        ImGui::ColorPicker3("##picker", reinterpret_cast<float *>(&currentDrawColor));
33        ImGui::EndPopup();
34    }
35    if (none_preset_color)
36        ImGui::PopStyleColor();
37}
38
39void WindowClass::DrawSizeSettings()
40{
41    ImGui::Text("Draw Size");
42    ImGui::SameLine();
43
44    // 다음 요소의 최대 가로길이를 지정. canvas를 넘지않도록 계산.
45    ImGui::PushItemWidth(canvasSize.x - ImGui::GetCursorPosX());
46
47    // Float 값범위를 사용하는 한개짜리 Slider이며, 1~10 까지 설정가능하다.
48    ImGui::SliderFloat("##drawSize", &pointDrawSize, 1.0F, 10.0F);
49    ImGui::PopItemWidth();
50}

Save / Load ImageFile #

내 맘대로 자체적인 포맷을 만들어서 바이너리 파일 저장, 로드

 1void WindowClass::SaveToImageFile(std::string_view filename)
 2{
 3    // 이진 파일은 2번째 인자로 std::ios::binary 를 넣어줘야한다. 
 4    auto out = std::ofstream(filename.data(), std::ios::binary);
 5
 6    
 7    // is_open 만 사용해도 된다. !연산자는 failbit, badbit가 설정된경우 true를 리턴해준다.
 8    if (!out || !out.is_open())
 9        return;
10
11    // 첫번째 값은 size_t (내환경에선 64bit) 크기의 PointData 벡터의 길이 쓰기
12    // write가 const char* 기준으로 작성되어있다. 
13    // 메모리상 숫자는 거꾸로 저장되어있지만, 어차피 앞바이트부터 1byte씩 쓰기 때문에 순서에 맞게 저장된다
14    const auto point_count = points.size();
15    out.write(reinterpret_cast<const char *>(&point_count),
16              sizeof(point_count));
17
18    // 포인트 벡터에서 그린 점들에 대한 정보를 하나씩(1px) 꺼내서 저장
19    for (const auto& [point, color, size] : points)
20    {
21        // 파일에 하나씩 point, color, size를 차곡차곡 저장
22        out.write(reinterpret_cast<const char *>(&point), sizeof(point));
23        out.write(reinterpret_cast<const char *>(&color), sizeof(color));
24        out.write(reinterpret_cast<const char *>(&size), sizeof(size));
25    }
26    out.close();
27}
28
29void WindowClass::LoadFromImageFile(std::string_view filename)
30{
31    auto in = std::ifstream(filename.data(), std::ios::binary);
32
33    if (!in || !in.is_open())
34        return;
35
36    auto point_count = std::size_t{0};
37    in.read(reinterpret_cast<char *>(&point_count), sizeof(point_count));
38
39    for (std::size_t i = 0; i < point_count; ++i)
40    {
41        auto point = ImVec2{};
42        auto color = ImColor{};
43        auto size = float{};
44
45        in.read(reinterpret_cast<char *>(&point), sizeof(point));
46        in.read(reinterpret_cast<char *>(&color), sizeof(color));
47        in.read(reinterpret_cast<char *>(&size), sizeof(size));
48
49        points.push_back(std::make_tuple(point, color, size));
50    }
51    in.close();
52
53}

DrawCanvas #

 1void WindowClass::DrawCanvas()
 2{
 3    canvasPos = ImGui::GetCursorPos();    // canvas 위치
 4    const auto border_thickness = 1.5F;   // boorder 두께
 5
 6    // 캔버스의 실제 사이즈. 원하는 사이즈 + border 4방향 추가한 크기
 7    const auto button_size = ImVec2(canvasSize.x + 2.0F * border_thickness,
 8                                    canvasSize.y + 2.0F * border_thickness);
 9    // 사실 canvas는 Button이다.
10    ImGui::InvisibleButton("##canvas", button_size);
11
12    // mouse 좌표를 얻어오고, 마우스가 이전요소(InvisibleButton)에 Hovering되어있는지 확인
13    const auto mouse_pos = ImGui::GetMousePos();
14    const auto is_mouse_hovering = ImGui::IsItemHovered();
15
16    // 버튼 위에서 좌클릭한 경우
17    if (is_mouse_hovering && ImGui::IsMouseDown(ImGuiMouseButton_Left))
18    {
19        // points에 들어가는 값은 border 등에 영향받지 않은 0,0부터 시작되게 하기위함
20        const auto point = ImVec2(mouse_pos.x - canvasPos.x - border_thickness,
21                                  mouse_pos.y - canvasPos.y - border_thickness);
22        // 점 하나 저장. 이 점들은 push_back되어 위치순이 아니라 시간순으로 정렬된다. 
23        points.push_back(
24            std::make_tuple(point, currentDrawColor, pointDrawSize));
25    }
26
27    // 나중에 렌더링 과정에서 그려줄 작업 리스트를 가져온다.
28    auto *draw_list = ImGui::GetWindowDrawList();
29    for (const auto& [point, color, size] : points)
30    {
31        const auto pos = ImVec2(canvasPos.x + border_thickness + point.x,
32                                canvasPos.y + border_thickness + point.y);
33        // 작업 리스트에 색이 채워진 원을 추가한다.
34        draw_list->AddCircleFilled(pos, size, color);
35    }
36
37    // border 그리기
38    const auto border_min = canvasPos;
39    const auto border_max =
40        ImVec2(canvasPos.x + button_size.x - border_thickness,
41               canvasPos.y + button_size.y - border_thickness);
42    // 작업 리스트에 비어있는 사각형을 추가한다. 
43    draw_list->AddRect(border_min,
44                       border_max,
45                       IM_COL32(255, 255, 255, 255),
46                       0.0F,
47                       ImDrawCornerFlags_All,
48                       border_thickness);
49}
comments powered by Disqus