안녕하세요.

 

Project Cars 2 포스팅에서 말한대로 의자를 흔드는 모션 시뮬레이터를 만들기 위해,

현재 스텝 모터를 만지고 있고 소프트웨어 준비를 하고 있습니다.

 

2018/05/16 - [DIY/Project Cars 2] - Project Cars 2 SLI 완성!

 

 

무엇보다 차량의 서스펜션 값이 정말 궁금하더라구요.

그래서 그래프를 그려보기로 했습니다. 속도 그래프와 같이요.

그러면 풀 브레이킹을 할 때 앞으로 쏠리는 것이 보일 것이고, 기어 변속 충격도 눈으로 확인 할 수 있겠죠?

 

일단 자주 사용하는 Qt에서 그래프를 그려보도록 합니다.

정말 고맙게도 Qt에서 그래프를 그릴 수 있는 라이브러리는 오픈소스로 공개되어 있습니다.

 

기능이 막강한 만큼 사용법이 참 어려운데요,

라이브러리를 가져다가 메인 그래프 1개와 서브 그래프 4개를 같이 그리는 프로젝트를 올려보겠습니다.

 

이번 기회에 Git을 공부하고 있어요... 파일이 너무 많아서 여기에 올리는 것은 관리도 힘들 것 같고 복잡하겠죠.

제 Git 첫 프로젝트 주소는 https://github.com/LocoField/LocoGraph 입니다!

 

 

Start 버튼을 눌러 스레드를 시작하면 이렇게 그래프가 실시간으로 그려집니다.

마우스 드래그로 그래프 범위를 이동할 수도 있는데, 그래프 새로운 값을 그리는 중에는 자동으로 움직여서 의미 없을 것 같네요.

 

아직 버그가 많이 있을 수 있어요... 또한 데이터 저장을 포함한 다양한 기능이 있으면 좋겠군요.

계속 기능을 추가하여 개선해나가도록 하겠습니다.

 

Project Cars 2 데이터 뽑는 것은 branch를 따야겠네요.

그래프 기능 추가 안하고 프카 먼저 하고 싶은데...ㅋㅋㅋ

 

제 프로젝트를 포함하여 QCustomPlot 라이브러리 질문은 댓글로 달아주세요.

최대한 도와드리도록 하겠습니다!

 

 

참고 웹페이지:

[1] http://www.qcustomplot.com/

 

'코딩 > Qt' 카테고리의 다른 글

Qt Graph 그리기  (0) 2018.07.22
Qt PDF Viewer  (1) 2018.03.15

OpenCV에 cv::applyColorMap 함수가 있습니다.

함수 사용 방법을 읽어보면, grayscale 뿐만 아니라 색상 영상도 제공되는 칼라맵을 적용하여 색감을 완전히 변화시킬 수 있습니다.

 

기본적으로 제공되는 칼라맵은 아래와 같습니다.

 

Class

Scale

 

COLORMAP_AUTUMN

../../../../_images/colorscale_autumn.jpg

 

COLORMAP_BONE

../../../../_images/colorscale_bone.jpg

 

COLORMAP_COOL

../../../../_images/colorscale_cool.jpg

 

COLORMAP_HOT

../../../../_images/colorscale_hot.jpg

 

COLORMAP_HSV

../../../../_images/colorscale_hsv.jpg

 

COLORMAP_JET

../../../../_images/colorscale_jet.jpg

 

COLORMAP_OCEAN

../../../../_images/colorscale_ocean.jpg

 

COLORMAP_PINK

../../../../_images/colorscale_pink.jpg

 

COLORMAP_RAINBOW

../../../../_images/colorscale_rainbow.jpg

 

COLORMAP_SPRING

../../../../_images/colorscale_spring.jpg

 

COLORMAP_SUMMER

../../../../_images/colorscale_summer.jpg

 

COLORMAP_WINTER

../../../../_images/colorscale_winter.jpg

 

 

저는 색상 영상보다는 grayscale 영상을 주로 다루는데, 이 함수는 비교적 쓰임새가 많습니다.

픽셀값이 0-255 또는 0-65535, 심지어 0.0f-1.0f 범위의 grayscale 영상을 많이 다루는데,

이 함수를 적용하면 어떤 값이든 눈에 쉽게 보이므로 논문 및 프레젠테이션 작성 시 아주 유용합니다.

 

한 가지 예를 들자면, 저는 한 3D 모델에서 레퍼런스 모델과 편차(deviation)를 구하여 그 정밀도(accuracy, not precision)를 보여주고 있습니다.

쉽게, 3D 포인트마다 정밀도 값에 따라 colormap을 입히면 정밀도가 좋지 않은 부분을 한 눈에 찾을 수 있고, 그 형태를 쉽게 알 수 있죠.

 

 

보통 정밀도를 색으로 표현할 때, 정확한 부분을 초록색, 약간 벗어나는 부분을 노란색, -로 벗어나면 파란색, +로 벗어나면 빨간색으로 표시를 합니다.

 

OpenCV에서 제공하는 Jet 클래스를 사용할 수 있지만, 초록색 범위가 너무 적어 결과가 부정적으로 보이더군요.

Rainbow 클래스는 보라색을 포함하고 있어, 바로 가져다 쓸 수도 없네요.

 

게다가, 표현하고자 하는 정밀도 값의 step이 얼마나 될 지 모르니, lookup table 방법으로는 구현이 어렵습니다.

base color 맵을 lookup table로 정의하고 표현하고자 하는 크기에 따라 유동적으로 colormap 만드는 것이 필요합니다.

 

그냥 쉽게 만들어보죠. OpenCV는 오픈소스라구요!

 

 

ColorMap.cpp

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
namespace colormap
{
    using namespace cv;
 
    static Mat linspace(float x0, float x1, int n)
    {
        Mat pts(n, 1, CV_32FC1);
        float step = (x1 - x0) / (n - 1);
        forint i = 0; i < n; i++ )
            pts.at<float>(i, 0= x0 + i*step;
        return pts;
    }
 
    //------------------------------------------------------------------------------
    // cv::sortMatrixRowsByIndices
    //------------------------------------------------------------------------------
    static void sortMatrixRowsByIndices(InputArray _src, InputArray _indices, OutputArray _dst)
    {
        if( _indices.getMat().type() != CV_32SC1 )
            CV_Error(Error::StsUnsupportedFormat, "cv::sortRowsByIndices only works on integer indices!");
        Mat src = _src.getMat();
        std::vector<int> indices = _indices.getMat();
        _dst.create(src.rows, src.cols, src.type());
        Mat dst = _dst.getMat();
        for( size_t idx = 0; idx < indices.size(); idx++ ) {
            Mat originalRow = src.row(indices[idx]);
            Mat sortedRow = dst.row((int) idx);
            originalRow.copyTo(sortedRow);
        }
    }
 
    static Mat sortMatrixRowsByIndices(InputArray src, InputArray indices)
    {
        Mat dst;
        sortMatrixRowsByIndices(src, indices, dst);
        return dst;
    }
 
 
    static Mat argsort(InputArray _src, bool ascending = true)
    {
        Mat src = _src.getMat();
        if( src.rows != && src.cols != )
            CV_Error(Error::StsBadArg, "cv::argsort only sorts 1D matrices.");
        int flags = SORT_EVERY_ROW | (ascending ? SORT_ASCENDING : SORT_DESCENDING);
        Mat sorted_indices;
        sortIdx(src.reshape(11), sorted_indices, flags);
        return sorted_indices;
    }
 
    template <typename _Tp> static
        Mat interp1_(InputArray X_, InputArray Y_, InputArray XI_)
    {
        // sort input table
        std::vector<int> sort_indices = argsort(X_);
 
        Mat X = sortMatrixRowsByIndices(X_, sort_indices);
        Mat Y = sortMatrixRowsByIndices(Y_, sort_indices);
        Mat XI = XI_.getMat();
        Mat yi = Mat::zeros(XI.size(), XI.type()); // interpolated values
 
        forint i = 0; i < XI.rows; i++ )
        {
            int low = 0;
            int high = X.rows - 1;
 
            // set bounds
            if( XI.at<_Tp>(i, 0< X.at<_Tp>(low, 0) )
                high = 1;
            if( XI.at<_Tp>(i, 0> X.at<_Tp>(high, 0) )
                low = high - 1;
 
            // binary search
            while( (high - low) > )
            {
                const int c = low + ((high - low) >> 1);
                if( XI.at<_Tp>(i, 0> X.at<_Tp>(c, 0) ) {
                    low = c;
                }
                else {
                    high = c;
                }
            }
 
            // linear interpolation
            yi.at<_Tp>(i, 0+= Y.at<_Tp>(low, 0)
                + (XI.at<_Tp>(i, 0- X.at<_Tp>(low, 0))
                * (Y.at<_Tp>(high, 0- Y.at<_Tp>(low, 0))
                / (X.at<_Tp>(high, 0- X.at<_Tp>(low, 0));
        }
 
        return yi;
    }
 
    class ColorMap
    {
    public:
        virtual ~ColorMap() {}
 
        // Setup base map to interpolate from.
        virtual void init(int n) = 0;
 
        // Interpolates from a base colormap.
        static Mat linear_colormap(InputArray X,
                                   InputArray r, InputArray g, InputArray b,
                                   int n) {
            return linear_colormap(X, r, g, b, linspace(01, n));
        }
 
        // Interpolates from a base colormap.
        static Mat linear_colormap(InputArray X,
                                   InputArray r, InputArray g, InputArray b,
                                   float begin, float end, float n) {
            return linear_colormap(X, r, g, b, linspace(begin, end, cvRound(n)));
        }
 
        // Interpolates from a base colormap.
        static Mat ColorMap::linear_colormap(InputArray X,
                                             InputArray r, InputArray g, InputArray b,
                                             InputArray xi) {
            Mat lut;
            Mat planes[] = {
                    interp1_<float>(X, b, xi),
                    interp1_<float>(X, g, xi),
                    interp1_<float>(X, r, xi) };
            merge(planes, 3, lut);
            return lut; // for OpenGL renderer
        }
    };
 
    class Rainbow2 : public ColorMap
    {
    public:
        Rainbow2(int n) : ColorMap()
        {
            init(n);
        }
 
        void init(int n)
        {
            // breakpoints
            Mat X = linspace(01171);
            
            // define the basemap
            float r[] = { 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000,
            0.02352940.04705880.07058820.09411770.1176470.1411760.1647060.1882350.2117650.2352940.2588240.2823530.3058820.3294120.3529410.3764710.40.4235290.4470590.4705880.4941180.5176470.5411760.5647060.5882350.6117650.6352940.6588240.6823530.7058820.7294120.7529410.7764710.80.8235290.8470590.8705880.8941180.9176470.9411770.9647060.988235,
            111111111111111111111111111111111111111111};
            
            float g[] = { 00.02352940.04705880.07058820.09411770.1176470.1411760.1647060.1882350.2117650.2352940.2588240.2823530.3058820.3294120.3529410.3764710.40.4235290.4470590.4705880.4941180.5176470.5411760.5647060.5882350.6117650.6352940.6588240.6823530.7058820.7294120.7529410.7764710.80.8235290.8470590.8705880.8941180.9176470.9411770.9647060.988235,
            1111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
            0.9882350.9647060.9411770.9176470.8941180.8705880.8470590.8235290.80.7764710.7529410.7294120.7058820.6823530.6588240.6352940.6117650.5882350.5647060.5411760.5176470.4941180.4705880.4470590.4235290.40.3764710.3529410.3294120.3058820.2823530.2588240.2352940.2117650.1882350.1647060.1411760.1176470.09411770.07058820.04705880.0235294};
            
            float b[] = { 1111111111111111111111111111111111111111111,
            0.9882350.9647060.9411770.9176470.8941180.8705880.8470590.8235290.80.7764710.7529410.7294120.7058820.6823530.6588240.6352940.6117650.5882350.5647060.5411760.5176470.4941180.4705880.4470590.4235290.40.3764710.3529410.3294120.3058820.2823530.2588240.2352940.2117650.1882350.1647060.1411760.1176470.09411770.07058820.04705880.0235294,
            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000};
 
            // now build lookup table
            this->image_colorMap = ColorMap::linear_colormap(X,
                                                             Mat(1711, CV_32FC1, r).clone(), // red
                                                             Mat(1711, CV_32FC1, g).clone(), // green
                                                             Mat(1711, CV_32FC1, b).clone(), // blue
                                                             n);
        }
 
    public:
        cv::Mat image_colorMap;
    };
}
cs

 

 

Rainbow2 라는 클래스를 새로 만들고 초기화 함수에 원하는 색상 값을 base color로 정의합니다.

그리고 상속받은 ColorMap 클래스의 메서드를 이용하여 파라미터 n에 따라 lookup table을 만들면 끝입니다.

ColorMap 클래스와 미리 정의한 static 함수는 OpenCV 소스입니다. (필요없는 부분은 제거했습니다.)

 

저는 OpenGL 등의 renderer에서 사용할 목적이니, 127 라인대로 Rainbow2 클래스의 멤버 변수는 float 형식으로 r g b 값을 (CV_32FC3) 가지고 있습니다.

멤버 변수에 직접 인덱스로 엑세스하여 필요한 색상 값을 쉽게 얻어올 수 있겠네요.

 

아래는 400개의 Rainbow 색상을 만든 결과입니다.

 

 

 

질문은 언제든지 환영입니다!

 

 

참고자료:

[1] https://docs.opencv.org/2.4/modules/contrib/doc/facerec/colormaps.html

 

'코딩 > OpenCV' 카테고리의 다른 글

OpenCV Custom Color Map  (0) 2018.04.17

라즈베리파이나 아두이노를 다루면서 숫자 출력할 일은 꼭 있습니다.

그럴 때 엘리베이터에서 쉽게 볼 수 있는 'seven-segment display' 또는 'flexible numeric display (FND)'를 이용합니다.

 

 

이렇게 무식하게 생겼고, 10개 핀이 달렸습니다.

그리고 이 모듈을 여러 개 달아서 대량의 정보를 표시하자니, 배선이 지옥입니다.

그래서 multiplexer를 이용하거나 Multiplexed 4 digit display 모듈을 판매하고 있습니다.

 

하지만, 4자리도 보여주고 싶은 정보를 보여주기에 부족하다면요?

MAX7219 칩이 달린 제품을 이용하시면 됩니다.

 

두 제품을 소개합니다.

1) MAX7219 dot matrix module

2) MAX7219 8-digit 7-segment LED display

 

앞에 MAX7219 이름이 붙는 이유는 이 이름의 칩을 사용한 모듈이라는 것입니다.

LED 형태에 따라 쉽게 제어를 할 수 있도록 만들어놨죠.

중국산(?)이라 그런지 자유롭게 사용가능한 제품 이미지도 구하기 힘들지만, 국내에서도 쉽게 구할 수 있는 좋은 제품입니다.

 

통신 방식은 DATA(MOSI), CS(chip select), CLK(clock) 3핀을 사용하는 SPI입니다.

그리고 MAX7219 제품끼리 여러 개를 물려서 사용할 수 있는 큰 장점이 있습니다.

 

저는 소개한 두 제품을 가지고 Project Cars 2 전용 SLI 제작하려고 합니다.

이번 포스팅에서는 라즈베리 파이에서 라이브러리 없이 C++로 MAX7219 모듈을 제어하는 방법을 소개합니다.

 

SLI가 무엇인지 궁금하다면?

2018/03/20 - [DIY/Project Cars 2] - Raspberry Pi에서 Project Cars 2 UDP 데이터 받기

 

 

 

MAX7219와 라즈베리 파이를 연결

 

라즈베리 파이에서 SPI 통신하는 핀은 정해져있기 때문에 핀맵은 달라질 수가 없겠네요.

WiringPI 기준으로 12 14 10번. 물리적인 핀 번호는 19 23 24번 입니다.

나머지 2개인 5v와 GND만 빠지지 않게 잘 연결해주면 되겠습니다.

 

 

 

MAX7219.h

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// define pins for Raspberry PI
#define DATA        12
#define CLOCK       14
#define LOAD        10
 
// MAX7219 Registers
#define DECODE_MODE   0x09
#define INTENSITY     0x0a
#define SCAN_LIMIT    0x0b
#define SHUTDOWN      0x0c
#define DISPLAY_TEST  0x0f
 
void send_SPI_16bits(unsigned short data)
{
    for (int i = 16; i > 0; i--)
    {
        /* bitmask */
        unsigned short mask = << (i - 1);
 
        /* send data */
        digitalWrite(CLOCK, 0);
        digitalWrite(DATA, (data & mask) ? 0);
        digitalWrite(CLOCK, 1);
    
        /* no receive data */
        //digitalWrite(CLOCK, 0);
    }
}
 
void send_MAX7219(unsigned short reg_number, unsigned short data1, unsigned short data2)
{
    digitalWrite(LOAD, 1);
    send_SPI_16bits((reg_number << 8+ data2);
    send_SPI_16bits((reg_number << 8+ data1);
    digitalWrite(LOAD, 0); // to latch
    digitalWrite(LOAD, 1);
}
 
cs

 

MAX7219를 제어하기 위한 함수 2개가 구현된 헤더 파일입니다.

네, 알고 있어요... 억지로 2개를 제어하겠다는 막코드죠 ㅋㅋㅋㅋ

 

MAX7219를 제어하기 위한 함수가 있구요, 모듈이 2개이니 data1과 data2를 같이 입력받습니다.

언젠가..는 초기화 과정에서 모듈 개수를 입력받아 유연하게 데이터를 입력받는 클래스로 구현해야겠죠?

 

다른 함수는 SPI 프로토콜에 맞게 16비트 데이터를 보내는 함수입니다.

MAX7219에 맞게 구현했으니 당연히 데이터를 받지 않고 보내기만 하고 있습니다. 16번 반복하면서요.

 

그럼 이 함수를 사용하는 예제를 살펴볼까요?

 

 

main.cpp

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
 
#include <wiringPi.h>
 
#include "max7219.h"
 
void intHandler(int dummy)
{
    send_MAX7219(SHUTDOWN, 00);
    exit(0);
}
 
int main(int argc, char** argv)
{
    signal(SIGINT, intHandler);
    
    if( wiringPiSetup() < )
    {
        return 1;
    }
 
    pinMode(DATA, OUTPUT);
    pinMode(CLOCK, OUTPUT);
    pinMode(LOAD, OUTPUT);
 
    send_MAX7219(SCAN_LIMIT, 77);
    send_MAX7219(DECODE_MODE, 00);
    send_MAX7219(INTENSITY, 88);
    send_MAX7219(SHUTDOWN, 11);
 
    int digit_table[11= { 12648109121519195114127123};
    int dot_table[9][9=
    { // 128 64 32 16 8 4 2 1
    { 000015000}, // N
    { 01111111}, // 1
    { 01511158815}, // 2
    { 01511151115}, // 3
    { 099915111}, // 4
    { 01588151115}, // 5
    { 01588159915}, // 6
    { 015999111}, // 7
    { 000015888}, // R
    };
    
    send_MAX7219(1, digit_table[0], dot_table[0][1]);
    send_MAX7219(2, digit_table[0], dot_table[0][2]);
    send_MAX7219(3, digit_table[0], dot_table[0][3]);
    send_MAX7219(4, digit_table[0], dot_table[0][4]);
    send_MAX7219(5, digit_table[0], dot_table[0][5]);
    send_MAX7219(6, digit_table[0], dot_table[0][6]);
    send_MAX7219(7, digit_table[0], dot_table[0][7]);
    send_MAX7219(8, digit_table[0], dot_table[0][8]);
 
    //intHandler(0);
    return 0;
}
cs

 

25-27 줄에서 SPI 통신에 사용할 3핀을 WiringPI로 초기화해줍니다.

그 다음으로, MAX7219 모듈을 초기화하는 코드가 나옵니다.

 

1) SCAN_LIMIT 명령어로 초기화를 해주는데요, 값을 바꿀 이유는 없을 것 같습니다.

 

2) DECODE_MODE는 제가 작성한 예제에서는 사용하지 않습니다.

디코드 모드를 사용하면 FND 모듈에서 출력 가능한 숫자를 포함한 문자를 그대로 출력할 수 있습니다.

0부터 15까지 데이터를 보내서 7 segment display 답게 0-9 숫자 또는 A B C D E F 문자를 출력할 수 있습니다

라고 알고 있는데, 그럼 .은 어떻게 출력하지?

디코드 모드는 자세히 살펴보지 않아 정확한 내용은 datasheet를 봐야할 것 같네요.

 

디코드 모드를 사용하지 않는다면, 8비트 마스크를 OR 비트 연산한 값인 (비트가 겹치지 않으니 더하면 됩니다.) 0-255 사이의 데이터를 보내야 합니다.

필요에 따라 출력할 숫자의 비트 마스크를 미리 계산하고 테이블로 만들어두고 배열로 출력하면 됩니다. 저처럼 말이죠, 이게 더 편해요.

 

3) INTENSITY는 말그대로 밝기입니다.

15까지 올릴 수 있는데, 너무 밝으니 눈이 아프더라구요.

 

4) SHUTDOWN은 ON OFF 입니다.

define 이름이 이상하게 보이는데, 1을 보내서 키고 0을 보내서 쉽게 끌 수 있습니다.

 

 

34 라인은 FND 모듈의 숫자 테이블입니다. 0-9 숫자에다가 부분적으로 끄기 위해 맨 마지막에 0을 추가했습니다.

. 을 찍고 싶으면 테이블 값에 128을 더하면 됩니다.

 

35 라인은 도트 매트릭스에서 자동차 기어를 표시하기 위해 ---- (Neutral), 1-7, ┌ (Reverse) 를 추가했습니다.

8x8 도트이니 당연히 2차원 배열이죠.

숫자를 예쁘게 출력하기 위해 오른쪽에 치우쳐 출력했고, 왼쪽 빈 공간에는 다른 정보를 출력할 수 있겠군요.

 

48-55 라인은 초기화가 완료되었음을 표시하기 위해

위에서 만들어놓은 테이블을 참조하여 FND 모듈과 도트 매트릭스 모듈에 각각 00000000 과 ---- 를 표시하는 코드입니다.

 

그리고 LED는 켜진 채 종료됩니다.

종료를 시키지 않는 경우에는 57 라인의 주석을 해제하여 메인함수가 종료되기 전 LED를 끌 수 있습니다.

물론, 핸들러 덕분에 Ctrl+C로 종료할 때 역시 LED가 꺼집니다.

 

 

지금까지 두 개의 MAX7219 모듈에 제가 원하는 정보를 출력할 수 있었습니다.

그리고, 아주 간단했습니다. 제 주관적인 생각으로는 라이브러리를 사용하여 이 모듈을 다루면 너무 무거워지는 것 같네요.

물론 테이블을 만들 때 약간의 노가다가 필요하죠.

여러분은 어떤 정보를 출력하시나요? 유용한 테이블은 공유해요~

 

 

참고자료:

[1] https://en.wikipedia.org/wiki/Seven-segment_display

[2] https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus

 

'코딩 > Raspberry PI' 카테고리의 다른 글

Raspberry Pi에서 MAX7219 제어하기  (0) 2018.03.16

Qt는 메타데이터에서만 문제가 생기지 않는다면... 쉽고 빠르게 GUI 소프트웨어를 작성할 수 있는 프레임워크이죠!

소프트웨어 안에서 PDF 문서를 보여주고자 할 때도 쉽게 해결 할 수 있습니다.

 

PDF Viewer를 만들기 위한 클래스 생성 예제를 올려봅니다.

 

Qt로 작성된 poppler 라이브러리를 사용하였고, 이것은 XpdfReader 라이브러리를 기반으로 한다고 합니다.

저는 서브클래스를 만들어 확대/축소, 페이지 이동 기능만 추가하였습니다.

 

라이브러리는 https://poppler.freedesktop.org/ 에서 라이브러리를 다운받을 수 있습니다.

또는 아래에서 Visual Studio 2015 x64로 제가 빌드한 라이브러리를 다운받으실 수 있습니다.

 

poppler.zip

 

 

PDF 핸들링은 라이브러리에서 다 해주기 때문에 서브클래스는 많이 복잡하지 않습니다.

스케일 비율이 커지면 문서가 점점 짤릴텐데, 아직 마우스 드래그로 뷰를 움직이는 기능은 구현하지 않았습니다.

근데 마우스 드래그 처리가 귀찮을 뿐, Page::renderToImage 함수에서 x, y 파라미터에 값만 전달해주면 쉽게 구현할 수 있겠네요.

 

질문은 얼마든지 환영합니다.

그럼 뿅!

 

 

QDocumentWidget.h

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#pragma once
 
#include <QWidget>
#include <QLabel>
#include <QImage>
#include <QWheelEvent>
 
#include <poppler-qt5.h>
 
 
class QDocumentWidget : public QLabel
{
    Q_OBJECT
 
public:
    QDocumentWidget(QWidget* pParent = NULL);
    ~QDocumentWidget();
 
protected:
    Poppler::Document* m_pDocument;
    Poppler::Page* m_pDocPage;
    int m_page;
    int m_scale; // pdf 문서 렌더링 크기. 단위는 %.
 
protected:
    void wheelEvent(QWheelEvent* event);
    void mouseDoubleClickEvent(QMouseEvent* event);
 
public:
    bool loadDocument(std::string path);
    bool setPage(int page);
    void showDocument();
    void closeDocument();
 
};
 
cs

 

 

 

QDocumentWidget.cpp

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include "QDocumentWidget.h"
 
#include <QGuiApplication>
 
 
QDocumentWidget::QDocumentWidget(QWidget* pParent)
    : m_pDocument(nullptr)
    , m_pDocPage(nullptr)
    , m_page(0)
    , m_scale(150)
{
    setParent(pParent);
 
    setBackgroundRole(QPalette::Dark);
    setAlignment(Qt::AlignCenter);
    setAutoFillBackground(true);
    setScaledContents(false);
    setMouseTracking(true);
}
 
QDocumentWidget::~QDocumentWidget()
{
    closeDocument();
}
 
void QDocumentWidget::wheelEvent(QWheelEvent* event)
{
    Qt::KeyboardModifiers k = QGuiApplication::keyboardModifiers();
    if( k == Qt::ControlModifier )
    {
        if( event->angleDelta().y() < )
            m_scale = max(m_scale - 550);
        else
            m_scale = min(m_scale + 5500);
 
        showDocument();
    }
    else
    {
        if( event->angleDelta().y() > // 위로
            setPage(m_page - 1);
        else
            setPage(m_page + 1);
 
        showDocument();
    }
}
 
void QDocumentWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
    if( event->button() == Qt::LeftButton )
    {
        m_scale = 100;
        showDocument();
    }
}
 
bool QDocumentWidget::loadDocument(std::string path)
{
    if( m_pDocument != nullptr )
        closeDocument();
 
    m_pDocument = Poppler::Document::load(path.c_str());
 
    if( m_pDocument == nullptr )
        return false;
 
    return setPage(m_page);
}
 
bool QDocumentWidget::setPage(int page)
{
    if( page < return false;
 
    if( m_pDocument )
    {
        int nPages = m_pDocument->numPages();
 
        if( page >= nPages )
            return false;
 
        m_page = page;
        m_pDocPage = m_pDocument->page(m_page);
        return m_pDocPage != nullptr;
    }
 
    return false;
}
 
void QDocumentWidget::showDocument()
{
    if!m_pDocPage )
        setPage(m_page);
 
    if( m_pDocPage )
    {
        QImage image = m_pDocPage->renderToImage(m_scale, m_scale);
        setPixmap(QPixmap::fromImage(image));
    }
}
 
void QDocumentWidget::closeDocument()
{
    if( m_pDocument != nullptr )
    {
        delete m_pDocPage;
        m_pDocPage = nullptr;
 
        delete m_pDocument;
        m_pDocument = nullptr;
    }
}
 
cs

 

 

참고 웹페이지:

[1] https://github.com/danigm/poppler

[2] http://www.xpdfreader.com/

 

'코딩 > Qt' 카테고리의 다른 글

Qt Graph 그리기  (0) 2018.07.22
Qt PDF Viewer  (1) 2018.03.15
  1. 2018.09.19 14:06

    비밀댓글입니다

 

 

 

특정 폴더에 아주 많은 디렉토리가 있고, 각 디렉토리의 파일에 접근해야 할 경우 우선 디렉토리의 목록을 모두 가져와야겠죠.

예시로 첨부한 그림처럼 규칙이 어느 정도 보이면 문자열로 처리하면 될텐데, 그렇지 않을 경우 참 곤란합니다.

옛날에 디렉토리 목록을 나열할 방법이 없지는 않을테지만, 라이브러리를 이용하지 않는 이상 코드 라인이 길어지거나 C++ 표준이 아니겠지요.

 

C++17에서는 아주 쉽게 디렉토리 목록을 나열할 클래스를 제공합니다.

filesystem namespace의 directory_iterator 클래스인데요, 역시나 boost 라이브러리에서 넘어왔죠.

 

Visual Studio 2015에서는 해당 클래스를 실험적으로 제공하고 있습니다. C++17 표준이 확정되기 이전이라 그랬겠지요.

따라서 filesystem namespace 앞에 experimental namespace를 붙여야 합니다... ^^;

 

filesystem 헤더 파일을 포함하고 std::experimental::filesystem::directory_iterator 클래스를 사용하시면 되겠습니다.

 

 

예제코드:

1
2
3
4
5
6
7
8
std::string dir = "D:/test/";
std::vector<std::string> paths;
 
forauto& p : std::experimental::filesystem::directory_iterator(dir) )
{
    paths.push_back(p.path().string());
}
 
cs

 

 

참고자료:

[1] http://en.cppreference.com/w/cpp/experimental/fs/directory_iterator 

[2] https://stackoverflow.com/questions/612097/how-can-i-get-the-list-of-files-in-a-directory-using-c-or-c 

 

'코딩 > C++' 카테고리의 다른 글

C++ 디렉토리 목록 나열 - std::filesystem::directory_iterator  (0) 2018.02.22

+ Recent posts