本例来自于Design Pattern in Modern C++一书的适配器模式一节。但是作者的代码是基于MFC绘图的。MFC绑定Windows平台,在Mac和Linux平台上无法使用。本人使用OpenCV重写绘图部分。运行成功。
但是有点值得注意的是,本文中Drawer的draw方法不是一个回调方法。所以本例中的LineToCacheAdapter类,其实作用不大,MFC在CDialog类或者CView类隐藏后再次显示时,可能会调用OnPaint回调方法重绘窗体。所以缓存效果很明显,就是减少无必要的Points计算。
但是本例使用的OpenCV,没有找到重绘窗体的回调。所以不需要刷新窗体。基本上缓存意义不大,除非你画了两条一模一样的线。我的意思是起点和终点相同。
例子的思路其实也很简单。
把原先不能接收线对象的方法,改用LineToPointAdapter进行适配,适配成点集合,这样就可以画点了。
程序目录结构如下,

test/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(adapter_caching)
set(CMAKE_CXX_STANDARD 20)
add_definitions(-g)
find_package(Boost REQUIRED COMPONENTS
system
filesystem
serialization
program_options
thread
)
find_package(OpenCV REQUIRED )
find_package(glog REQUIRED)
include_directories(${Boost_INCLUDE_DIRS} /usr/local/include /usr/local/include/opencv4 /usr/local/iODBC/include /opt/snowflake/snowflakeodbc/include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_SOURCE_DIR}/../include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/)
LINK_DIRECTORIES(/usr/local/lib /usr/local/iODBC/lib /opt/snowflake/snowflakeodbc/lib/universal)
file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/death_handler/impl/*.cpp)
foreach( sourcefile ${APP_SOURCES} )
file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})
string(FIND "${filename}" "test.cpp" "TEMP")
if( NOT "${TEMP}" STREQUAL "-1" )
string(REPLACE ".cpp" "" file ${filename})
add_executable(${file} ${APP_SOURCES})
target_link_libraries(${file} ${Boost_LIBRARIES} ${OpenCV_LIBS})
target_link_libraries(${file} glog::glog ssl crypto libgtest.a libgmock.a iodbc iodbcinst libnanodbc.a pthread)
endif()
endforeach( sourcefile ${APP_SOURCES})
test/rect_draw_test.cpp
#include "death_handler/death_handler.h"
#include <glog/logging.h>
#include "rect_draw_cache.hpp"
#include <utility>
#include <gtest/gtest.h>
int main(int argc, char** argv) {
FLAGS_log_dir = "./";
FLAGS_alsologtostderr = true;
// 日志级别 INFO, WARNING, ERROR, FATAL 的值分别为0、1、2、3
FLAGS_minloglevel = 0;
Debug::DeathHandler dh;
google::InitGoogleLogging("./logs.log");
testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}
GTEST_TEST(MultitonTests, Multiton) {
DrawableObjects objs {
std::make_shared<VectorRectangle>(10,10,100,100),
std::make_shared<VectorRectangle>(30,30,60,60)
};
Drawer<LineToPointCacheAdapter> d{objs};
d.draw();
d.wait_to_dispose();
}
include/geometry.h
#ifndef _FREDRIC_GEOMETRY_H_
#define _FREDRIC_GEOMETRY_H_
#include <boost/functional/hash.hpp>
struct Point {
int x, y;
friend std::size_t hash_value(Point const& obj) {
std::size_t seed = 0x725C686F;
boost::hash_combine(seed, obj.x);
boost::hash_combine(seed, obj.y);
return seed;
}
};
struct Line {
Point start, end;
friend std::size_t hash_value(Line const& obj) {
std::size_t seed = 0x719E6B16;
boost::hash_combine(seed, obj.start);
boost::hash_combine(seed, obj.end);
return seed;
}
};
struct VectorObject {
virtual std::vector<Line>::iterator begin() = 0;
virtual std::vector<Line>::iterator end() = 0;
};
struct VectorRectangle: VectorObject {
VectorRectangle(int x, int y, int width, int height) {
lines.emplace_back(Line{Point{x, y}, Point{x+width, y}});
lines.emplace_back(Line{Point{x+width, y}, Point{x+width, y+height}});
lines.emplace_back(Line{Point{x, y}, Point{x, y+height}});
lines.emplace_back(Line{Point{x, y+height}, Point{x+width, y+height}});
}
std::vector<Line>::iterator begin() {
return lines.begin();
}
std::vector<Line>::iterator end() {
return lines.end();
}
private:
std::vector<Line> lines;
};
#endif
include/rect_draw_cache.hpp
#ifndef _FREDRIC_RECT_DRAW_CACHE_HPP_
#define _FREDRIC_RECT_DRAW_CACHE_HPP_
#include "geometry.h"
#include <opencv2/opencv.hpp>
#include <glog/logging.h>
#include <iostream>
#include <memory>
#include <map>
#include <string>
#include <concepts>
#include <vector>
std::string const window_name = "Draw Points";
using DrawableObjects = std::vector<std::shared_ptr<VectorObject>>;
using Points = std::vector<Point>;
int const EXIT_KEY = 27;
struct LineToPointAdapter {
LineToPointAdapter(Line& line){
static int count = 0;
LOG(INFO) << ++count << ": Generating points for line (no caching)\n";
// no interpolation
int left = std::min(line.start.x, line.end.x);
int right = std::max(line.start.x, line.end.x);
int top = std::min(line.start.y, line.end.y);
int bottom = std::max(line.start.y, line.end.y);
int dx = right - left;
int dy = bottom - top;
// only vertical or horizontal lines
if (dx == 0){
// vertical
for (int y = top; y <= bottom; ++y){
points.emplace_back(Point{ left,y });
}
// horizontal
} else if (dy == 0) {
for (int x = left; x <= right; ++x) {
points.emplace_back(Point{ x, top });
}
}
}
Points::iterator begin() {
return points.begin();
}
Points::iterator end() {
return points.end();
}
private:
Points points;
};
struct LineToPointCacheAdapter {
LineToPointCacheAdapter(Line& line){
// functional objects
boost::hash<Line> hash;
line_hash = hash(line);
// find it, don't need to calculate it again
if(cache.find(line_hash) != cache.end()) {
return;
}
static int count = 0;
LOG(INFO) << ++count << ": Generating points for line (caching)\n";
Points points;
// no interpolation
int left = std::min(line.start.x, line.end.x);
int right = std::max(line.start.x, line.end.x);
int top = std::min(line.start.y, line.end.y);
int bottom = std::max(line.start.y, line.end.y);
int dx = right - left;
int dy = bottom - top;
// only vertical or horizontal lines
if (dx == 0){
// vertical
for (int y = top; y <= bottom; ++y){
points.emplace_back(Point{ left,y });
}
// horizontal
} else if (dy == 0) {
for (int x = left; x <= right; ++x) {
points.emplace_back(Point{ x, top });
}
}
// not find, cache it
cache[line_hash] = points;
}
Points::iterator begin() {
return cache[line_hash].begin();
}
Points::iterator end() {
return cache[line_hash].end();
}
private:
std::size_t line_hash;
static std::map<std::size_t, Points> cache;
};
std::map<std::size_t, Points> LineToPointCacheAdapter::cache {};
template <typename AdapterType>
struct Drawer {
Drawer(DrawableObjects const& vector_objs_): vector_objs{vector_objs_} {
cv::namedWindow(window_name, cv::WINDOW_NORMAL);
cv::resizeWindow(window_name, 200, 150);
img_ = cv::Mat::zeros(cv::Size(200, 150), CV_8UC1);
img_.setTo(cv::Scalar(255));
cv::imshow(window_name, img_);
cv::setWindowProperty(window_name, cv::WND_PROP_TOPMOST, 1);
}
void wait_to_dispose() {
while(EXIT_KEY != cv::waitKey(1000)) {
}
}
void draw() {
for(auto& o: vector_objs) {
for(auto& l: *o) {
AdapterType lpo{l};
draw_points(lpo.begin(), lpo.end());
}
}
}
private:
DrawableObjects vector_objs;
cv::Mat img_;
void draw_points(Points::iterator start, Points::iterator end) {
for(auto i=start; i!=end; ++i) {
cv::circle(img_, cv::Point(i->x, i->y), 3, cv::Scalar(0), cv::FILLED, cv::LINE_8);
}
cv::imshow(window_name, img_);
}
};
#endif
程序输出如下,


网友评论