j-view模块
目录配置:
JCad
include
j-view
j-view.h
j-view
prj
CMakeLists.txt
src
j-view.cpp
CMakeLists.txt
存在多种计算机图形学的引擎,用于将图形绘制到显卡缓存并显示出来。有些引擎使用CPU的能力,有些则充分发挥GPU的能力,而对于CAD/CAM这种面向大型绘图设计的图形软件,不仅仅需要充分发挥硬件的能力,而且需要进行多种图形学、算法的优化才能达到满足用户需求的目的。
这里我们暂时使用Qt提供的QPainter作为绘图引擎,该引擎是使用CPU来进行绘图,理解容易、使用简单。后续将实现基于OpenGL引擎的绘图方式,这里定义了一个USE_QPAINTER宏用于区分使用了哪一种绘图引擎。
cmake_minimum_required(VERSION 3.5)
project(j-view)
add_library(
j-view
SHARED
)
find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
target_link_libraries(
j-view
PRIVATE
Qt5::Widgets
Qt5::Core
j-geometry)
add_compile_definitions(USE_QPAINTER=1) #define macro to use QPAINTER
include_directories(
${QT_INCLUDE_DIR}
../../include
../src
)
target_sources(
j-view
PRIVATE
../src/j-view.cpp
)
QT5_WRAP_CPP(
MOC_FILES
../../include/j-view/j-view.h
)
include(${CMAKE_SOURCE_DIR}/cmake/common.cmake)
头文件 j-view.h
需要使用QPainter在一块画布上进行绘图,想象一下你手握画笔蘸取不同的颜色来在一块画图上绘图各种不同的图形,这里的画布是一个QImage的数字图像对象。这里图像的大小与视图尺寸相等,正好覆盖在整个视图上,并且由一个个相邻的像素点按照矩阵排列组成。
而对于图形坐标系和视图坐标系的缩放比例ratio_,以及图形坐标系的原点在视图坐标系下的坐标位置(x_orig_geo_, y_orig_geo_)可以参照上一篇关于坐标系的说明。
//j-view.h
#pragma once
#include <QObject>
#include <QPainter>
#include <QImage>
#include <vector>
#include <algorithm>
#include "j-geometry/j-geometry-point.h"
#include "j-geometry/j-geometry-line.h"
#include "j-geometry/j-geometry-circle.h"
namespace JNSView {
class JView : public QObject {
Q_OBJECT
public:
JView(int width, int height);
~JView();
/// draw geometry APIs
void DrawAllGeo(const std::vector<JNSGeometry::JGeometry*>& geos);
void DrawPoint(JNSGeometry::JGeoPoint* point);
void DrawLine(JNSGeometry::JGeoLine* line);
void DrawCircle(JNSGeometry::JGeoCircle* circle);
/// coordinate convert
JNSGeometry::JGeoPoint GeoToView(const JNSGeometry::JGeoPoint& point_geo);
JNSGeometry::JGeoPoint ViewToGeo(const JNSGeometry::JGeoPoint& point_view);
/// static APIs
static QPointF GeoPointToQPointF(const JNSGeometry::JGeoPoint& geo_point);
private:
#ifdef USE_QPAINTER
QPainter painter_;
#endif
QImage buffer_;
double ratio_ = 1.0; // geo/view
double x_orig_geo_ = 0.0;
double y_orig_geo_ = 0.0;
};
}
源文件j-view.cpp
GeoToView和ViewToGeo接口用于实现上一篇坐标系中关于仿射变换的方法。GeoToView将图形坐标系下的坐标点转换为视图坐标系下的像素坐标,而ViewToGeo接口则将视图坐标系下的像素坐标转换为图形坐标系下的坐标点。
#include "j-view/j-view.h"
namespace JNSView {
JView::JView(int width, int height) : QObject() {
buffer_ = QImage(width, height, QImage::Format::Format_ARGB32);
}
JView::~JView() {}
void JView::DrawAllGeo(const std::vector<JNSGeometry::JGeometry*>& geos) {
std::for_each(geos.begin(), geos.end(), [&](JNSGeometry::JGeometry* geo) {
if(geo->IsGeoPoint()){
DrawPoint(static_cast<JNSGeometry::JGeoPoint*>(geo));
} else if (geo->IsGeoLine()) {
DrawLine(static_cast<JNSGeometry::JGeoLine*>(geo));
} else if (geo->IsGeoCircle()) {
DrawCircle(static_cast<JNSGeometry::JGeoCircle*>(geo));
}
});
}
void JView::DrawPoint(JNSGeometry::JGeoPoint* point) {
painter_.drawPoint(GeoPointToQPointF(GeoToView(*point)));
}
void JView::DrawLine(JNSGeometry::JGeoLine* line) {
painter_.drawLine(GeoPointToQPointF(GeoToView(line->GetStart())), GeoPointToQPointF(GeoToView(line->GetEnd())));
}
void JView::DrawCircle(JNSGeometry::JGeoCircle* circle) {
Q_UNUSED(circle)
}
JNSGeometry::JGeoPoint JView::GeoToView(const JNSGeometry::JGeoPoint& point_geo){
double x_pixel = x_orig_geo_ + point_geo.GetX()/ratio_;
double y_pixel = y_orig_geo_ + point_geo.GetY()/ratio_;
return JNSGeometry::JGeoPoint(x_pixel, y_pixel);
}
JNSGeometry::JGeoPoint JView::ViewToGeo(const JNSGeometry::JGeoPoint& point_view){
double x_physical = (point_view.GetX() - x_orig_geo_)*ratio_;
double y_physical = (point_view.GetY() - y_orig_geo_)*ratio_;
return JNSGeometry::JGeoPoint(x_physical, y_physical);
}
QPointF JView::GeoPointToQPointF(const JNSGeometry::JGeoPoint& geo_point) {
return QPointF(geo_point.GetX(), geo_point.GetY());
}
}
这里有很多细节实现需要注意,后续需要持续的优化。例如
- 点point的绘制,如果只占据一个像素点,那么对于用户而言需要举着放大镜进行观察才能明确地确认这是个点图元,而不是屏幕上的灰尘;所以点图元应该占据一定的像素面积。
- 绘制不同图元的接口可以设置为同名函数,并一定需要利用函数名称加以甄别等。