From e806adfbceef2ab718ebd00b3d42cf94c44ff025 Mon Sep 17 00:00:00 2001 From: gb <741021719@qq.com> Date: Wed, 24 Jan 2024 12:05:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A3=81=E5=88=87=E7=AE=97?= =?UTF-8?q?=E6=B3=95=EF=BC=88=E5=B0=9A=E6=9C=89BUG=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sane-opts/auto_crop.txt | 145 +++++++++ hardware/hardware.cpp | 8 +- hardware/hardware.h | 2 +- imgproc/algs/ImageProcess_Public.cpp | 424 +++++++++++++++++++++++++++ imgproc/algs/ImageProcess_Public.h | 144 +++++++++ imgproc/algs/auto_crop.cpp | 337 +++++++++++++++++++++ imgproc/algs/auto_crop.h | 39 +++ imgproc/imgprc_mgr.cpp | 3 + sdk/base/paper.cpp | 7 + sdk/base/paper.h | 1 + sdk/base/plat_types.h | 3 + sdk/base/words.h | 16 + xmake.lua | 4 +- 13 files changed, 1126 insertions(+), 7 deletions(-) create mode 100644 docs/sane-opts/auto_crop.txt create mode 100644 imgproc/algs/ImageProcess_Public.cpp create mode 100644 imgproc/algs/ImageProcess_Public.h create mode 100644 imgproc/algs/auto_crop.cpp create mode 100644 imgproc/algs/auto_crop.h create mode 100644 sdk/base/words.h diff --git a/docs/sane-opts/auto_crop.txt b/docs/sane-opts/auto_crop.txt new file mode 100644 index 0000000..fd62bd0 --- /dev/null +++ b/docs/sane-opts/auto_crop.txt @@ -0,0 +1,145 @@ +{ + "is-anti-skew": { + "cat": "imgp", + "group": "imgp", + "title": "自动纠偏", + "desc": "自动纠正歪斜送入的文稿图像", + "type": "bool", + "pos": 30, + "fix-id": 34844, + "ui-pos": -1, + "auth": 0, + "size": 4, + "cur": true, + "default": true, + "depend": "page!=对折" + }, + "is-erase-black-frame": { + "cat": "imgp", + "group": "imgp", + "title": "消除黑框", + "desc": "消除文稿范围外的黑色背景", + "type": "bool", + "pos": 35, + "fix-id": 34849, + "ui-pos": -1, + "auth": 0, + "size": 4, + "cur": true, + "default": true + }, + "bkg-fill-mode": { + "cat": "imgp", + "group": "imgp", + "title": "背景填充方式", + "desc": "选择背景填充方式", + "type": "string", + "pos": 36, + "fix-id": 34854, + "ui-pos": -1, + "auth": 0, + "size": 16, + "cur": "凸多边形", + "default": "凸多边形", + "range": ["凸多边形", "凹多边形"], + "depend": "is-erase-black-frame==true" + }, + "threshold": { + "cat": "imgp", + "group": "imgp", + "title": "阈值", + "desc": "文稿底色与黑色背景灰度值的差值大于该值,才会被识别为文稿", + "type": "int", + "pos": 37, + "fix-id": 34851, + "ui-pos": -1, + "auth": 0, + "size": 4, + "cur": 40, + "default": 40, + "range": { + "min": 30, + "max": 50, + "step": 1 + }, + "depend": "is-erase-black-frame==true||paper==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true" + }, + "anti-noise-level": { + "cat": "imgp", + "group": "imgp", + "title": "背景抗噪等级", + "desc": "能够容忍的背景杂色条纹的宽度", + "type": "int", + "pos": 38, + "fix-id": 34852, + "ui-pos": -1, + "auth": 0, + "size": 4, + "cur": 8, + "default": 8, + "range": { + "min": 2, + "max": 20, + "step": 1 + }, + "depend": "is-erase-black-frame==true||paper==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true" + }, + "margin": { + "cat": "imgp", + "group": "imgp", + "title": "边缘缩进", + "desc": "寻找文稿边缘时对边缘的侵入程度", + "type": "int", + "pos": 40, + "fix-id": 34853, + "ui-pos": -1, + "auth": 0, + "size": 4, + "cur": 5, + "default": 5, + "range": { + "min": 2, + "max": 30, + "step": 1 + }, + "depend": "is-erase-black-frame==true||paper==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true" + }, + "paper": { + "cat": "base", + "group": "base", + "title": "纸张尺寸", + "desc": "设置出图大小", + "type": "string", + "pos": 1000, + "fix-id": 34831, + "ui-pos": -1, + "auth": 0, + "size": 44, + "cur": "匹配原始尺寸", + "default": "匹配原始尺寸", + "range": ["A3", "8开", "A4", "16开", "A5", "A6", "B4", "B5", "B6", "Letter", "Double Letter", "LEGAL", "匹配原始尺寸", { + "resolution<500": "最大扫描尺寸自动裁切" + }, { + "resolution<500": "最大扫描尺寸" + }, { + "resolution<500": "三联试卷" + }] + }, + "lateral": { + "cat": "base", + "group": "base", + "title": "横向", + "desc": "横向放置纸张", + "type": "bool", + "pos": 1000, + "fix-id": 34924, + "ui-pos": -1, + "auth": 0, + "affect": 6, + "visible": 0, + "size": 4, + "cur": false, + "default": false, + "depend": "paper==A4 || paper==16开 || paper==A5 || paper==A6 || paper==B5 || paper==B6 || paper==Letter" + } +} \ No newline at end of file diff --git a/hardware/hardware.cpp b/hardware/hardware.cpp index 30ebd30..ad08b43 100644 --- a/hardware/hardware.cpp +++ b/hardware/hardware.cpp @@ -81,10 +81,10 @@ void scanner_hw::init(void) auto m = [this](void* value) -> void { - if(strcmp((char*)value, "\347\201\260\345\272\246") == 0) + if(strcmp((char*)value, WORDS_COLOR_GRAY) == 0) mode_ = (char*)value; else - mode_ = "\345\275\251\350\211\262"; + mode_ = WORDS_COLOR_COLOR; }; auto r = [this](void* value) -> void { @@ -204,7 +204,7 @@ void scanner_hw::init(void) }; auto dbchk = [this](void* value) -> void { - double_chk_ = strcmp((char*)value, "\347\246\201\347\224\250") != 0; + double_chk_ = strcmp((char*)value, WORDS_FORBIDDEN) != 0; }; auto motsp = [this](void* value) -> void { @@ -240,7 +240,7 @@ void scanner_hw::init(void) }; OPT_HANDLER(cntm) { - scan_cntless_ = strcmp((char*)value, "\350\277\236\347\273\255\346\211\253\346\217\217") == 0; + scan_cntless_ = strcmp((char*)value, WORDS_SCAN_CONTINUOUS) == 0; }; OPT_HANDLER(cnt) { diff --git a/hardware/hardware.h b/hardware/hardware.h index 03ada79..272867d 100644 --- a/hardware/hardware.h +++ b/hardware/hardware.h @@ -78,7 +78,7 @@ class scanner_hw : public sane_opt_provider double stretch_h_ = 1.0f; double stretch_v_ = 1.0f; - std::string paper_ = "\345\214\271\351\205\215\345\216\237\345\247\213\345\260\272\345\257\270"; + std::string paper_ = WORDS_PAPER_ORIGIN_SIZE; bool lateral_ = false; bool lateral_en_ = false; bool staple_chk_ = true; diff --git a/imgproc/algs/ImageProcess_Public.cpp b/imgproc/algs/ImageProcess_Public.cpp new file mode 100644 index 0000000..2834ded --- /dev/null +++ b/imgproc/algs/ImageProcess_Public.cpp @@ -0,0 +1,424 @@ +#include "ImageProcess_Public.h" +#include +#include +#include + +namespace hg +{ + void convexHull(const std::vector& src, std::vector& dst, bool clockwise) + { + CvMemStorage* storage = cvCreateMemStorage(); // + CvSeq* ptseq = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, sizeof(CvContour), sizeof(CvPoint), storage); //ptseqstorage + + //srcĵ㼯ptseq + for (const cv::Point& item : src) + { + CvPoint p; + p.x = item.x; + p.y = item.y; + cvSeqPush(ptseq, &p); + } + + //ȡ + CvSeq* hull = cvConvexHull2(ptseq, nullptr, clockwise ? CV_CLOCKWISE : CV_COUNTER_CLOCKWISE, 0); + + if (hull == nullptr) + { + //ͷstorage + cvReleaseMemStorage(&storage); + return; + } + + //dst + dst.clear(); + for (int i = 0, hullCount = hull->total; i < hullCount; i++) + dst.push_back(**CV_GET_SEQ_ELEM(CvPoint*, hull, i)); + + //ͷstorage + cvReleaseMemStorage(&storage); + } + +#define R_COLOR 255 + void fillConvexHull(cv::Mat& image, const std::vector& points) + { + uint index_top = 0; + uint index_bottom = 0; + for (size_t i = 0, length = points.size(); i < length; i++) + { + if (points[i].y < points[index_top].y) + index_top = i; + if (points[i].y > points[index_bottom].y) + index_bottom = i; + } + + std::vector edge_left; + uint temp = index_top; + while (temp != index_bottom) + { + edge_left.push_back(points[temp]); + temp = (temp + points.size() - 1) % points.size(); + } + edge_left.push_back(points[index_bottom]); + + std::vector edge_right; + temp = index_top; + while (temp != index_bottom) + { + edge_right.push_back(points[temp]); + temp = (temp + points.size() + 1) % points.size(); + } + edge_right.push_back(points[index_bottom]); + + std::vector left_edge_x; + std::vector left_edge_y; + for (size_t i = 0, length = edge_left.size() - 1; i < length; i++) + { + int y_top = edge_left[i].y; + int x_top = edge_left[i].x; + int y_bottom = edge_left[i + 1].y; + int x_bottom = edge_left[i + 1].x; + for (int y = y_top; y < y_bottom; y++) + if (y >= 0 && y_top != y_bottom && y < image.rows) + { + left_edge_x.push_back(((x_bottom - x_top) * y + x_top * y_bottom - x_bottom * y_top) / (y_bottom - y_top)); + left_edge_y.push_back(y); + } + } + size_t step = image.step; + unsigned char* ptr; + ptr = image.data + static_cast(left_edge_y[0]) * step; + for (size_t i = 0, length = left_edge_x.size(); i < length; i++) + { + int pix = left_edge_x[i]; + if (pix < image.cols - 1 && pix > 0) + memset(ptr + i * step, R_COLOR, static_cast((pix + 1) * image.channels())); + } + + std::vector right_edge_x; + std::vector right_edge_y; + for (size_t i = 0, length = edge_right.size() - 1; i < length; i++) + { + int y_top = edge_right[i].y; + int x_top = edge_right[i].x; + int y_bottom = edge_right[i + 1].y; + int x_bottom = edge_right[i + 1].x; + for (int y = y_top; y < y_bottom; y++) + if (y_top != y_bottom && y < image.rows && y >= 0) + { + right_edge_x.push_back(((x_bottom - x_top) * y + x_top * y_bottom - x_bottom * y_top) / (y_bottom - y_top)); + right_edge_y.push_back(y); + } + } + + ptr = image.data + static_cast(right_edge_y[0]) * step; + for (size_t i = 0, length = right_edge_x.size(); i < length; i++) + { + int pix = right_edge_x[i]; + if (pix < image.cols - 1 && pix > 0) + memset(ptr + i * step + pix * image.channels(), R_COLOR, step - static_cast(pix * image.channels())); + } + + if (edge_left[0].y > 0) + memset(image.data, R_COLOR, static_cast(edge_left[0].y) * step); + + if (edge_left.back().y < image.rows - 1) + memset(image.data + static_cast(edge_left.back().y) * step, R_COLOR, + static_cast(image.rows - edge_left.back().y) * step); + } + + void fillPolys(cv::Mat& image, const std::vector>& contours, const cv::Scalar& color) + { + if (contours.empty()) return; + + size_t count = contours.size(); + cv::Point** pointss = new cv::Point*[count]; + int* npts = new int[count]; + + for (size_t i = 0; i < count; i++) + { + size_t length = contours[i].size(); + npts[i] = length; + pointss[i] = new cv::Point[length]; + for (size_t j = 0; j < length; j++) + pointss[i][j] = contours[i][j]; + } + cv::fillPoly(image, const_cast(pointss), npts, count, color); + + for (size_t i = 0; i < count; i++) + delete[] pointss[i]; + + delete[] pointss; + delete[] npts; + } + + void findContours(const cv::Mat& src, std::vector>& contours, std::vector& hierarchy, int retr, int method, cv::Point offset) + { + CvMat c_image; + c_image = cvMat(src.rows, src.cols, src.type(), src.data); + c_image.step = src.step[0]; + c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (src.flags & cv::Mat::CONTINUOUS_FLAG); + + cv::MemStorage storage(cvCreateMemStorage()); + CvSeq* _ccontours = nullptr; + +#if CV_VERSION_REVISION <= 6 + cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint(offset)); +#else + cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint{ offset.x, offset.y }); +#endif + if (!_ccontours) + { + contours.clear(); + return; + } + cv::Seq all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage)); + size_t total = all_contours.size(); + contours.resize(total); + + cv::SeqIterator it = all_contours.begin(); + for (size_t i = 0; i < total; i++, ++it) + { + CvSeq* c = *it; + reinterpret_cast(c)->color = static_cast(i); + int count = c->total; + int* data = new int[static_cast(count * 2)]; + cvCvtSeqToArray(c, data); + for (int j = 0; j < count; j++) + { + contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1])); + } + delete[] data; + } + + hierarchy.resize(total); + it = all_contours.begin(); + for (size_t i = 0; i < total; i++, ++it) + { + CvSeq* c = *it; + int h_next = c->h_next ? reinterpret_cast(c->h_next)->color : -1; + int h_prev = c->h_prev ? reinterpret_cast(c->h_prev)->color : -1; + int v_next = c->v_next ? reinterpret_cast(c->v_next)->color : -1; + int v_prev = c->v_prev ? reinterpret_cast(c->v_prev)->color : -1; + hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev); + } + + storage.release(); + } + + cv::Scalar getBackGroundColor(const cv::Mat& image, const cv::Mat& mask, int threshold) + { + float range[] = { 0, 256 }; + const float* ranges = { range }; + int histSize = 256; + cv::Scalar bgc; + if (image.channels() == 3) + { + cv::Mat mv[3]; + cv::split(image, mv); + + cv::Mat hist[3]; + for (int i = 0; i < 3; i++) + { + cv::calcHist(&mv[i], 1, 0, mask, hist[i], 1, &histSize, &ranges); + + int index_max = 0; + int max_value = 0; + for (size_t j = threshold; j < 256; j++) + if (hist[i].at(j) > max_value) + { + index_max = j; + max_value = hist[i].at(j); + } + + bgc[i] = index_max; + } + } + else + { + cv::Mat hist; + cv::calcHist(&image, 1, 0, mask, hist, 1, &histSize, &ranges); + int index_max = 0; + int max_value = 0; + for (size_t j = threshold; j < 256; j++) + if (hist.at(j) > max_value) + { + index_max = j; + max_value = hist.at(j); + } + bgc = cv::Scalar::all(index_max); + } + + return bgc; + } + + cv::RotatedRect getBoundingRect(const std::vector& contour) + { + if (contour.empty()) return {}; + + cv::RotatedRect rect = minAreaRect(contour); + if (rect.angle < -45) + { + rect.angle += 90; + float temp = rect.size.width; + rect.size.width = rect.size.height; + rect.size.height = temp; + } + if (rect.angle > 45) + { + rect.angle -= 90; + float temp = rect.size.width; + rect.size.width = rect.size.height; + rect.size.height = temp; + } + + return rect; + } + + std::vector getMaxContour(const std::vector>& contours, const std::vector& hierarchy) + { + std::vector maxContour; + if (contours.size() < 1) return {}; + + for (size_t i = 0, length = hierarchy.size(); i < length; i++) + if (hierarchy[i][3] == -1) + for (const auto &item : contours[i]) + maxContour.push_back(item); + + return maxContour; + } + + std::vector getVertices(const cv::RotatedRect& rect) + { + cv::Point2f box[4]; + rect.points(box); + std::vector points; + for (int i = 0; i < 4; i++) + points.push_back(cv::Point(box[i])); + + return points; + } + + void polyIndent(std::vector& points, const cv::Point& center, int indent) + { + static cv::Point zero(0, 0); + for (cv::Point& item : points) + { +#if 0 + cv::Point vec = item - center; + if (vec != zero) + { + int length = vec.x * vec.x + vec.y * vec.y; + float x = cv::sqrt(static_cast(vec.x * vec.x / length)) * indent; + float y = cv::sqrt(static_cast(vec.y * vec.y / length)) * indent; + + if (vec.x < 0) x *= -1.0f; + if (vec.y < 0) y *= -1.0f; + + item.x -= static_cast(x); + item.y -= static_cast(y); + } +#else + if (item.x > center.x) + item.x -= indent; + else + item.x += indent; + + if (item.y > center.y) + item.y -= indent; + else + item.y += indent; +#endif + } + } + + cv::Mat transforColor(const cv::Mat& src) + { + if (src.channels() == 1) return src.clone(); + + std::vector channels(3); + cv::split(src, channels); + + cv::Mat temp, dst; + bitwise_or(channels[0], channels[1], temp); + bitwise_or(channels[2], temp, dst); + temp.release(); + + for (cv::Mat& index : channels) + index.release(); + return dst; + } + +#define DEFAULT_THRESHOLD 30 +#define THRESHOLD_OFFSET 10 + void threshold_Mat(const cv::Mat& src, cv::Mat& dst, double thre) + { + cv::Mat temp; + if (src.channels() == 3) + { +#ifdef USE_ONENCL + if (cl_res.context) + transforColor_threshold_opencl(src, dst, static_cast(thre)); + else +#endif + { + //temp = transforColor(src); + cv::cvtColor(src, temp, cv::COLOR_BGR2GRAY); + } + } + else + temp = src; + + if (thre > 0) + cv::threshold(temp, dst, thre, 255, cv::THRESH_BINARY); + else + { + std::vector unusual_cols; + std::vector unusual_gray; + + uchar* firstLine = temp.ptr(0); + uchar* lastLine = temp.ptr(temp.rows - 1); + + uchar temp_gray; + for (size_t i = 0, length = temp.step; i < length; i++) + { + temp_gray = cv::min(firstLine[i], lastLine[i]); + if (temp_gray > DEFAULT_THRESHOLD) + { + unusual_cols.push_back(i); + unusual_gray.push_back(temp_gray); + } + } + + cv::threshold(temp, dst, DEFAULT_THRESHOLD + THRESHOLD_OFFSET, 255, cv::THRESH_BINARY); + + for (size_t i = 0; i < unusual_cols.size(); i++) + dst(cv::Rect(unusual_cols[i], 0, 1, temp.rows)) = 0; + } + } + + cv::Point warpPoint(const cv::Point& p, const cv::Mat& warp_mat) + { + double src_data[3] = { static_cast(p.x), static_cast(p.y), 1 }; + cv::Mat src(3, 1, warp_mat.type(), src_data); //warp_mat.type() == CV_64FC1 + + cv::Mat dst = warp_mat * src; + double* ptr = reinterpret_cast(dst.data); + return cv::Point(static_cast(ptr[0]), static_cast(ptr[1])); + } + + int distanceP2P(const cv::Point& p1, const cv::Point& p2) + { + return cv::sqrt(cv::pow(p1.x - p2.x, 2) + cv::pow(p1.y - p2.y, 2)); + } + + float distanceP2L(const cv::Point& p, const cv::Point& l1, const cv::Point& l2) + { + //ֱ߷ + int A = 0, B = 0, C = 0; + A = l1.y - l2.y; + B = l2.x - l1.x; + C = l1.x * l2.y - l1.y * l2.x; + //㵽ֱ߾빫ʽ + return ((float)abs(A * p.x + B * p.y + C)) / ((float)sqrtf(A * A + B * B)); + } +} \ No newline at end of file diff --git a/imgproc/algs/ImageProcess_Public.h b/imgproc/algs/ImageProcess_Public.h new file mode 100644 index 0000000..c988fc9 --- /dev/null +++ b/imgproc/algs/ImageProcess_Public.h @@ -0,0 +1,144 @@ +/* + * ==================================================== + + * ܣͼ㷨ֹܻܿImageProcess෴ʹ + * ߣά + * ʱ䣺2020/4/21 + * ޸ʱ䣺2020/4/21 + * 2021/07/12 v1.1 getBoundingRectУӿǾʼ angle > 90 + * 2021/07/22 v1.2 convexHullУ޸㼯Ϊտܵ±BUG + * 2023/11/02 v1.3 threshold_Mat,thre<0ʱӦֵ + * 2023/12/01 v1.4 getBackGroundColor㷨 + * 2023/12/02 v1.4.1 getBackGroundColorthresholdֵ + * 2023/12/04 v1.4.2 opencv汾ӿڡ + * 2023/12/05 v1.4.3 getBackGroundColorֵ֧ͨͼ񱳾ɫʶ + * 汾ţv1.4.3 + + * ==================================================== + */ + +#ifndef IMAGE_PROCESS_PUBLIC_H +#define IMAGE_PROCESS_PUBLIC_H + +#include "opencv2/opencv.hpp" +#include + +namespace hg +{ + /* + * ܣԴ㼯͹㼯 + * src: Դ㼯 + * dst: Ŀ㼯 + * clockwise: trueΪ˳ʱfalseΪʱ + */ + void convexHull(const std::vector& src, std::vector& dst, bool clockwise = false); + + /* + * ܣ͹ΣĬɫΪɫ + * image: ͼ + * points: ͹㼯ʱ + */ + void fillConvexHull(cv::Mat& image, const std::vector& points); + + /* + * ܣ䰼 + * image: ͼ + * contours: 㼯ʱ + * color: ɫ + */ + void fillPolys(cv::Mat& image, const std::vector>& contours, const cv::Scalar& color); + + /* + * ܣȡͨ + * src: Դͼ + * contours: + * hierarchy: ϵcontoursӦretrѡͬб仯 + * retr: ʽĬΪʽ + * method: 㷨ѡĬΪͨ + * offset: ʼ㣬ĬΪ0,0 + */ + void findContours(const cv::Mat& src, std::vector>& contours, std::vector& hierarchy, + int retr = cv::RETR_LIST, int method = cv::CHAIN_APPROX_SIMPLE, cv::Point offset = cv::Point(0, 0)); + + /// + /// ȡͼƬĸɫ + /// + /// ͼͨ + /// Ĥ + /// ֵųɫ + /// ĸɫ + cv::Scalar getBackGroundColor(const cv::Mat& image, const cv::Mat& mask = cv::Mat(), int threshold = 20); + + /* + * ܣȡǵ㼯СӾ + * contour: 㼯 + * ֵ: ת + */ + cv::RotatedRect getBoundingRect(const std::vector& contour); + + /* + * : ȡС͹ + * contours: ÿɵ㼯ɣ + * hierarchy: У֮ĹϵcontoursӦ + * ֵ: ͹㼯 + */ + std::vector getMaxContour(const std::vector>& contours, const std::vector& hierarchy); + + /* + * : ȡС͹ + * contours: ÿɵ㼯ɣ + * hierarchy: У֮ĹϵcontoursӦ + * ֵ: ͹㼯 + */ + std::vector getVertices(const cv::RotatedRect& rect); + + /* + * : + * points: 㼯 + * center: Χcenter + * indent: + */ + void polyIndent(std::vector& points, const cv::Point& center, int indent); + + /* + * : ֵܹɫͻҶͼsrcΪɫͼʱҶͼȡֵͨ + * src: Դͼ + * dst: Ŀͼ + * thre: ֵthre < 0ʱñɫΪÿӦֵ + */ + void threshold_Mat(const cv::Mat& src, cv::Mat& dst, double thre); + + /* + * : ɫתҶȣҶͼȡֵͨ + * src: Դͼ + * ֵ: Ҷͼ + */ + cv::Mat transforColor(const cv::Mat& src); + + /* + * : ȡķ任 + * p: ԭ + * warp_mat: 任ϵ + * ֵ: 任ĵ + */ + cv::Point warpPoint(const cv::Point& p, const cv::Mat& warp_mat); + + /* + * : 㵽 + * p1: 1 + * p2: 2 + * ֵ: 㵽 + */ + int distanceP2P(const cv::Point& p1, const cv::Point& p2); + + /* + * : 㵽ֱ߾ + * p: + * l1: ֱ߶˵1 + * l2: ֱ߶˵2 + * ֵ: 㵽ֱ߾ + */ + float distanceP2L(const cv::Point& p, const cv::Point& l1, const cv::Point& l2); +} + +#endif // !IMAGE_PROCESS_C_H diff --git a/imgproc/algs/auto_crop.cpp b/imgproc/algs/auto_crop.cpp new file mode 100644 index 0000000..8e3df1d --- /dev/null +++ b/imgproc/algs/auto_crop.cpp @@ -0,0 +1,337 @@ +#include "auto_crop.h" + + +#include +#include +#include +#include +#include "ImageProcess_Public.h" + + +#define FRONT_TOP 70 +#define FX_FY 0.5f + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +static std::string device_opt_json[] = { + "{\"is-anti-skew\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u81ea\\u52a8\\u7ea0\\u504f\",\"desc\":\"\\u81ea\\u52a8\\u7ea0\\u6b63\\u6b6a\\u659c\\u9001\\u5165\\u7684\\u6587\\u7a3f\\u56fe\\u50cf\",\"type\":\"bool\",\"pos\":30,\"fix-id\":34844,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":true,\"default\":true,\"depend\":\"page!=\\u5bf9\\u6298\"},\"is-erase-black-frame\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u6d88\\u9664\\u9ed1\\u6846\",\"desc\":\"\\u6d88\\u9664\\u6587\\u7a3f\\u8303\\u56f4\\u5916\\u7684\\u9ed1\\u8272\\u80cc\\u666f\",\"type\":\"bool\",\"pos\":35,\"fix-id\":34849,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":true,\"default\":true},\"bkg-fill-mode\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u80cc\\u666f\\u586b\\u5145\\u65b9\\u5f0f\",\"desc\":\"\\u9009\\u62e9\\u80cc\\u666f\\u586b\\u5145\\u65b9\\u5f0f\",\"type\":\"string\",\"pos\":36,\"fix-id\":34854,\"ui-pos\":-1,\"auth\":0,\"size\":16,\"cur\":\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"default\":\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"range\":[\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"\\u51f9\\u591a\\u8fb9\\u5f62\"],\"depend\":\"is-erase-black-frame==true\"},\"threshold\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u9608\\u503c\",\"desc\":\"\\u6587\\u7a3f\\u5e95\\u8272\\u4e0e\\u9ed1\\u8272\\u80cc\\u666f\\u7070\\u5ea6\\u503c\\u7684\\u5dee\\u503c\\u5927\\u4e8e\\u8be5\\u503c\\uff0c\\u624d\\u4f1a\\u88ab\\u8bc6\\u522b\\u4e3a\\u6587\\u7a3f\",\"type\":\"int\",\"pos\":37,\"fix-id\":34851,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":40,\"default\":40,\"range\":{\"min\":30,\"max\":50,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"anti-noise-level\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u80cc\\u666f\\u6297\\u566a\\u7b49\\u7ea7\",\"desc\":\"\\u80fd\\u591f\\u5bb9\\u5fcd\\u7684\\u80cc\\u666f\\u6742\\u8272\\u6761\\u7eb9\\u7684\\u5bbd\\u5ea6\",\"type\":\"int\",\"pos\":38,\"fix-id\":34852,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":8,\"default\":8,\"range\":{\"min\":2,\"max\":20,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"margin\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u8fb9\\u7f18\\u7f29\\u8fdb\",\"desc\":\"\\u5bfb\\u627e\\u6587\\u7a3f\\u8fb9\\u7f18\\u65f6\\u5bf9\\u8fb9\\u7f18\\u7684\\u4fb5\\u5165\\u7a0b\\u5ea6\",\"type\":\"int\",\"pos\":40,\"fix-id\":34853,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":5,\"default\":5,\"range\":{\"min\":2,\"max\":30,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"paper\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u7eb8\\u5f20\\u5c3a\\u5bf8\",\"desc\":\"\\u8bbe\\u7f6e\\u51fa\\u56fe\\u5927\\u5c0f\",\"type\":\"string\",\"pos\":1000,\"fix-id\":34831,\"ui-pos\":-1,\"auth\":0,\"size\":44,\"cur\":\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",\"default\":\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",\"range\":[\"A3\",\"8\\u5f00\",\"A4\",\"16\\u5f00\",\"A5\",\"A6\",\"B4\",\"B5\",\"B6\",\"Letter\",\"Double Letter\",\"LEGAL\",\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",{\"resolution<500\":\"\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207\"},{\"resolution<500\":\"\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\"},{\"resolution<500\":\"\\u4e09\\u8054\\u8bd5\\u5377\"}]},\"lateral\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u6a2a\\u5411\",\"desc\":\"\\u6a2a\\u5411\\u653e\\u7f6e\\u7eb8\\u5f20\",\"type\":\"bool\",\"pos\":1000,\"fix-id\":34924,\"ui-pos\":-1,\"auth\":0,\"affect\":6,\"visible\":0,\"size\":4,\"cur\":false,\"default\":false,\"depend\":\"paper==A4 || paper==16\\u5f00 || paper==A5 || paper==A6 || paper==B5 || paper==B6 || paper==Letter\"}}" +}; + +static void myWarpAffine(cv::InputArray _src, cv::OutputArray _dst, cv::InputArray _M0, cv::Size dsize, int flags, int borderType, const cv::Scalar& borderValue) +{ + int interpolation = flags; + cv::Mat src = _src.getMat(), M0 = _M0.getMat(); + cv::Mat dst = _dst.getMat(); + + if (dst.data == src.data) + src = src.clone(); + + double M[6] = { 0 }; + cv::Mat matM(2, 3, CV_64F, M); + if (interpolation == cv::INTER_AREA) + interpolation = cv::INTER_LINEAR; + + M0.convertTo(matM, matM.type()); + + if (!(flags & cv::WARP_INVERSE_MAP)) + { + double D = M[0] * M[4] - M[1] * M[3]; + D = D != 0 ? 1. / D : 0; + double A11 = M[4] * D, A22 = M[0] * D; + M[0] = A11; M[1] *= -D; + M[3] *= -D; M[4] = A22; + double b1 = -M[0] * M[2] - M[1] * M[5]; + double b2 = -M[3] * M[2] - M[4] * M[5]; + M[2] = b1; M[5] = b2; + } + + cv::hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, + M, interpolation, borderType, borderValue.val); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +#define OPTION_FUNC(name) auto name = [this](void* val) -> void + +auto_crop::auto_crop() : image_processor("auto_crop") +{ + init(); + + ADD_THIS_JSON(); +} +auto_crop::~auto_crop() +{} + +void auto_crop::init(void) +{ + OPTION_FUNC(deskew) + { + deskew_ = *(bool*)val; + }; + OPTION_FUNC(bg) + { + fill_bg_ = *(bool*)val; + }; + OPTION_FUNC(bgm) + { + convex_ = strcmp((char*)val, WORDS_FILLBG_CONVEX) == 0; + }; + OPTION_FUNC(thr) + { + threshold_ = *(int*)val; + }; + OPTION_FUNC(noise) + { + noise_ = *(int*)val; + }; + OPTION_FUNC(margin) + { + indent_ = *(int*)val; + }; + OPTION_FUNC(paper) + { + crop_ = strcmp((char*)val, WORDS_PAPER_ORIGIN_SIZE) == 0 + || strcmp((char*)val, WORDS_PAPER_MAX_SIZE_CROP) == 0; + if(crop_) + memset(&fixed_paper_, 0, sizeof(fixed_paper_)); + else + { + fixed_paper_ = paper::size((char*)val); + if(lateral_) + paper::swap_size(fixed_paper_); + } + }; + OPTION_FUNC(lat) + { + if(lateral_ != *(bool*)val) + paper::swap_size(fixed_paper_); + lateral_ = *(bool*)val; + }; + + opt_handler_[SANE_FULL_NAME(ANTI_SKEW)] = deskew; + opt_handler_[SANE_FULL_NAME(ERASE_BLACK_FRAME)] = bg; + opt_handler_[SANE_FULL_NAME(FILL_BKG_MODE)] = bgm; + opt_handler_[SANE_FULL_NAME(THRESHOLD)] = thr; + opt_handler_[SANE_FULL_NAME(ANTI_NOISE_LEVEL)] = noise; + opt_handler_[SANE_FULL_NAME(MARGIN)] = margin; + opt_handler_[SANE_FULL_NAME(PAPER)] = paper; + opt_handler_[SANE_FULL_NAME(LATERAL)] = lat; +} +int auto_crop::work(PROCIMGINFO& in, PROCIMGINFO& out) +{ + int ret = SCANNER_ERR_OK, + dWidth = fixed_paper_.cx, + dHeight = fixed_paper_.cy; + + cv::Mat thre; + + hg::threshold_Mat(in.img, thre, threshold_); + + if (noise_ > 0) + cv::morphologyEx(thre, thre, cv::MORPH_OPEN, getStructuringElement(cv::MORPH_RECT, cv::Size(cv::max(static_cast(noise_ * FX_FY), 1), 1)), + cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar::all(0)); + + std::vector hierarchy; + std::vector> contours; + + hg::findContours(thre, contours, hierarchy, cv::RETR_EXTERNAL); + + for (std::vector& sub : contours) + for (cv::Point& p : sub) + p /= FX_FY; + + std::vector maxContour = hg::getMaxContour(contours, hierarchy); + + if (maxContour.empty()) + { + if (crop_) + out.img = in.img.clone(); + else + { + cv::Rect roi = cv::Rect((in.img.cols - dWidth) / 2, FRONT_TOP, dWidth, dHeight) & cv::Rect(0, 0, in.img.cols, in.img.rows); + out.img = in.img(roi).clone(); + } + return ret; + } + + cv::RotatedRect rect = hg::getBoundingRect(maxContour); + if (rect.size.width < 1 || rect.size.height < 1) + { + out.img = in.img; + return ret; + } + + cv::Scalar blankColor; + if (fill_bg_) + if (/*isColorBlank*/1) + { + cv::Rect boudingRect = cv::boundingRect(maxContour); + boudingRect.x *= FX_FY; + boudingRect.y *= FX_FY; + boudingRect.width *= FX_FY; + boudingRect.height *= FX_FY; + + cv::Mat temp_bgc; + cv::resize(in.img(boudingRect), temp_bgc, cv::Size(200, 200)); + blankColor = hg::getBackGroundColor(temp_bgc, cv::Mat(), 10); + } + else + blankColor = cv::Scalar::all(255); + else + blankColor = cv::Scalar::all(0); + + if (crop_) + if (deskew_) + out.img = cv::Mat(cv::Size(rect.size), in.img.type(), blankColor); + else + out.img = cv::Mat(/*rect.boundingRect().size()*/ cv::boundingRect(maxContour).size(), in.img.type(), blankColor); + else + out.img = cv::Mat(dHeight, dWidth, in.img.type(), blankColor); + + cv::Mat dstROI; + if (deskew_ && rect.angle != 0) + { + cv::RotatedRect rect_temp = rect; + if (rect_temp.size.width > out.img.cols) + rect_temp.size.width = out.img.cols; + if (rect_temp.size.height > out.img.rows) + rect_temp.size.height = out.img.rows; + + cv::Point2f srcTri[4], dstTri[3]; + rect_temp.points(srcTri); + srcTri[0].x -= 1; + srcTri[1].x -= 1; + srcTri[2].x -= 1; + + int w = rect_temp.size.width; + int h = rect_temp.size.height; + int x = (out.img.cols - w) / 2; + int y = (out.img.rows - h) / 2; + dstTri[0] = cv::Point2f(0, h); + dstTri[1] = cv::Point2f(0, 0); + dstTri[2] = cv::Point2f(w, 0); + + dstROI = out.img(cv::Rect(x, y, w, h) & cv::Rect(0, 0, out.img.cols, out.img.rows)); + myWarpAffine(in.img, dstROI, cv::getAffineTransform(srcTri, dstTri), dstROI.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, blankColor); + } + else + { + cv::Rect bounding = cv::boundingRect(maxContour); + + if (bounding.width > out.img.cols) + { + bounding.x += (bounding.width - out.img.cols) / 2; + bounding.width = out.img.cols; + } + + if (bounding.height > out.img.rows) + { + bounding.y += (bounding.height - out.img.rows) / 2; + bounding.height = out.img.rows; + } + + dstROI = out.img(cv::Rect((out.img.cols - bounding.width) / 2, (out.img.rows - bounding.height) / 2, bounding.width, bounding.height)); + in.img(bounding).copyTo(dstROI); + + //cv::imwrite("out.img.bmp", out.img); + } + + if (fill_bg_) + { + if (convex_) + { + hg::convexHull(maxContour, maxContour); + contours.clear(); + contours.push_back(maxContour); + } + + cv::Point2f srcTri[4], dstTri[3]; + int w, h; + if (deskew_ && rect.angle != 0) + { + rect.points(srcTri); + srcTri[0].x -= 1; + srcTri[1].x -= 1; + srcTri[2].x -= 1; + w = rect.size.width; + h = rect.size.height; + } + else + { + //cv::Rect bounding = rect.boundingRect(); + cv::Rect bounding = cv::boundingRect(maxContour); + srcTri[0] = cv::Point(bounding.x, bounding.br().y - 1); + srcTri[1] = cv::Point(bounding.x, bounding.y); + srcTri[2] = cv::Point(bounding.br().x - 1, bounding.y); + w = bounding.width; + h = bounding.height; + } + + dstTri[0] = cv::Point2f((dstROI.cols - w) / 2 + indent_, (dstROI.rows - h) / 2 + h - indent_); + dstTri[1] = cv::Point2f((dstROI.cols - w) / 2 + indent_, (dstROI.rows - h) / 2 + indent_); + dstTri[2] = cv::Point2f((dstROI.cols - w) / 2 - indent_ + w, (dstROI.rows - h) / 2 + indent_); + cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri); + + double* ptr_m = reinterpret_cast(warp_mat.data); + double a = ptr_m[0]; + double b = ptr_m[1]; + double c = ptr_m[2]; + double d = ptr_m[3]; + double e = ptr_m[4]; + double f = ptr_m[5]; + + int x, y; + for (std::vector& sub : contours) + for (cv::Point& p : sub) + { + x = p.x; + y = p.y; + p.x = static_cast(a * x + b * y + c); + p.y = static_cast(d * x + e * y + f); + } + + contours.push_back(std::vector()); + contours[contours.size() - 1].push_back(cv::Point(-1, dstROI.rows - 1)); + contours[contours.size() - 1].push_back(cv::Point(-1, -1)); + contours[contours.size() - 1].push_back(cv::Point(dstROI.cols, -1)); + contours[contours.size() - 1].push_back(cv::Point(dstROI.cols, out.img.rows)); + hg::fillPolys(dstROI, contours, blankColor); + } + + return ret; +} + +int auto_crop::set_value(const char* name/*nullptr for all options*/, void* val/*nullptr for restore*/) +{ + int ret = SCANNER_ERR_OK; + + if(opt_handler_.count(name)) + opt_handler_[name](val); + else + ret = SCANNER_ERR_DEVICE_NOT_SUPPORT; + + return ret; +} +int auto_crop::process(std::vector& in, std::vector& out) +{ + int ret = SCANNER_ERR_OK; + + for(auto& v: in) + { + PROCIMGINFO o; + chronograph watch; + + ret = work(v, o); + o.info = v.info; + o.info.prc_time = watch.elapse_ms(); + o.info.prc_stage = get_position(); + o.info.width = o.img.cols; + o.info.height = o.img.rows; + + out.push_back(o); + } + + return ret; +} diff --git a/imgproc/algs/auto_crop.h b/imgproc/algs/auto_crop.h new file mode 100644 index 0000000..4d789d4 --- /dev/null +++ b/imgproc/algs/auto_crop.h @@ -0,0 +1,39 @@ +// perform crop, deskew, fill background ... +// +// Date: 2024-01-24 +#pragma once + +#include +#include +#include + +class auto_crop : public image_processor +{ + bool crop_ = true; // auto crop + bool deskew_ = true; + bool fill_bg_ = true; + bool convex_ = true; + bool fill_clr_ = false; + int threshold_ = 10; + int indent_ = 5; + int noise_ = 8; + SIZE fixed_paper_ = {0, 0}; + bool lateral_ = false; + + std::map> opt_handler_; + + void init(void); + int work(PROCIMGINFO& in, PROCIMGINFO& out); + +public: + auto_crop(); + +protected: + ~auto_crop(); + +public: + virtual int set_value(const char* name/*nullptr for all options*/, void* val/*nullptr for restore*/) override; + +public: + virtual int process(std::vector& in, std::vector& out) override; +}; diff --git a/imgproc/imgprc_mgr.cpp b/imgproc/imgprc_mgr.cpp index c044451..28d643c 100644 --- a/imgproc/imgprc_mgr.cpp +++ b/imgproc/imgprc_mgr.cpp @@ -7,6 +7,7 @@ #include "./algs/rebuild.h" #include "./algs/stretch.h" +#include "./algs/auto_crop.h" static std::string device_opt_json[] = { "{\"is-multiout\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u591a\\u6d41\\u8f93\\u51fa\",\"desc\":\"\\u540c\\u65f6\\u8f93\\u51fa\\u591a\\u79cd\\u989c\\u8272\\u6a21\\u5f0f\\u7684\\u56fe\\u50cf\",\"type\":\"bool\",\"fix-id\":34817,\"ui-pos\":10,\"auth\":0,\"size\":4,\"cur\":false,\"default\":false},\"multiout-type\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u591a\\u6d41\\u8f93\\u51fa\\u7c7b\\u578b\",\"desc\":\"\\u9009\\u62e9\\u591a\\u6d41\\u8f93\\u51fa\\u7684\\u7c7b\\u578b\",\"type\":\"string\",\"fix-id\":34818,\"ui-pos\":11,\"auth\":0,\"enabled\":false,\"size\":66,\"cur\":\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"default\":\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"range\":[\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"\\u5f69\\u8272+\\u7070\\u5ea6\",\"\\u5f69\\u8272+\\u9ed1\\u767d\",\"\\u7070\\u5ea6+\\u9ed1\\u767d\"],\"depend\":\"is-multiout==true\"},\"mode\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u989c\\u8272\\u6a21\\u5f0f\",\"desc\":\"\\u9009\\u62e9\\u8272\\u5f69\\u6a21\\u5f0f\",\"type\":\"string\",\"fix-id\":34819,\"ui-pos\":15,\"auth\":0,\"size\":24,\"cur\":\"24\\u4f4d\\u5f69\\u8272\",\"default\":\"24\\u4f4d\\u5f69\\u8272\",\"range\":[\"24\\u4f4d\\u5f69\\u8272\",\"256\\u7ea7\\u7070\\u5ea6\",\"\\u9ed1\\u767d\",\"\\u989c\\u8272\\u81ea\\u52a8\\u8bc6\\u522b\"],\"depend\":\"is-multiout!=true\"},\"dump-img\":{\"cat\":\"base\",\"group\":\"\\u9ad8\\u7ea7\\u8bbe\\u7f6e\",\"title\":\"\\u8f93\\u51fa\\u4e2d\\u95f4\\u56fe\\u50cf\",\"desc\":\"\\u8f93\\u51fa\\u5404\\u7b97\\u6cd5\\u4e2d\\u95f4\\u7ed3\\u679c\\u56fe\\u50cf\",\"type\":\"bool\",\"ui-pos\":10,\"auth\":0,\"affect\":2,\"size\":4,\"cur\":false,\"default\":false}}" @@ -201,9 +202,11 @@ int imgproc_mgr::load_processor(const char* path) // ADD_IMG_PROCESSOR(rebuild); ADD_IMG_PROCESSOR(stretch); + ADD_IMG_PROCESSOR(auto_crop); std::sort(processors_.begin(), processors_.end(), &imgproc_mgr::sort_processor_by_pos); + return ret; } int imgproc_mgr::clear(void) diff --git a/sdk/base/paper.cpp b/sdk/base/paper.cpp index 2928047..de4da3e 100644 --- a/sdk/base/paper.cpp +++ b/sdk/base/paper.cpp @@ -61,4 +61,11 @@ namespace paper { return paperi.size(name); } + + void swap_size(SIZE& s) + { + s.cx ^= s.cy; + s.cy ^= s.cx; + s.cx ^= s.cy; + } }; diff --git a/sdk/base/paper.h b/sdk/base/paper.h index a181749..c1e29eb 100644 --- a/sdk/base/paper.h +++ b/sdk/base/paper.h @@ -9,4 +9,5 @@ namespace paper { // get known paper size in mm SIZE size(const char* name); + void swap_size(SIZE& s); }; diff --git a/sdk/base/plat_types.h b/sdk/base/plat_types.h index 98237aa..a4364e0 100644 --- a/sdk/base/plat_types.h +++ b/sdk/base/plat_types.h @@ -137,3 +137,6 @@ extern uint64_t GetCurrentThreadId(void); #define pid_t int #endif + + +#include "words.h" diff --git a/sdk/base/words.h b/sdk/base/words.h new file mode 100644 index 0000000..5184458 --- /dev/null +++ b/sdk/base/words.h @@ -0,0 +1,16 @@ +// const multi-bytes-words definition +// +// Date: 2024-01-24 +#pragma once + +#define WORDS_COLOR_COLOR "\345\275\251\350\211\262" +#define WORDS_COLOR_GRAY "\347\201\260\345\272\246" + +#define WORDS_PAPER_ORIGIN_SIZE "\345\214\271\351\205\215\345\216\237\345\247\213\345\260\272\345\257\270" +#define WORDS_PAPER_MAX_SIZE_CROP "\346\234\200\345\244\247\346\211\253\346\217\217\345\260\272\345\257\270\350\207\252\345\212\250\350\243\201\345\210\207" + +#define WORDS_FORBIDDEN "\347\246\201\347\224\250" + +#define WORDS_SCAN_CONTINUOUS "\350\277\236\347\273\255\346\211\253\346\217\217" + +#define WORDS_FILLBG_CONVEX "\345\207\270\345\244\232\350\276\271\345\275\242" diff --git a/xmake.lua b/xmake.lua index c265a71..cb0547a 100644 --- a/xmake.lua +++ b/xmake.lua @@ -60,8 +60,8 @@ add_packagedirs("sdk") add_defines("BUILD_AS_DEVICE") add_defines("VER_MAIN=2") add_defines("VER_FAMILY=200") -add_defines("VER_DATE=20240123") -add_defines("VER_BUILD=32") +add_defines("VER_DATE=20240124") +add_defines("VER_BUILD=8") target("conf") set_kind("phony")