From 6c29336660e9945f36d1d76f1c8f5babbbf2b778 Mon Sep 17 00:00:00 2001 From: yizhi <946185759@qq.com> Date: Mon, 17 Mar 2025 10:19:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 45 ++++ .gitignore | 9 + CMakeLists.txt | 89 ++++++++ README.md | 39 ++++ cxx/mat.cc | 450 +++++++++++++++++++++++++++++++++++++ cxx/node.cc | 11 + cxx/node.h | 10 + cxx/util.cc | 35 +++ package.json | 19 ++ src/cv/addon.ts | 19 ++ src/cv/common.ts | 14 ++ src/cv/consts.ts | 166 ++++++++++++++ src/cv/draw.ts | 104 +++++++++ src/cv/index.ts | 5 + src/cv/mat.ts | 55 +++++ src/cv/proc.ts | 53 +++++ src/index.ts | 3 + src/test.ts | 34 +++ test_data/im1.jpeg | Bin 0 -> 85606 bytes thirdpart/cmake-js-util.js | 18 ++ thirdpart/install.js | 175 +++++++++++++++ tsconfig.json | 108 +++++++++ 22 files changed, 1461 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cxx/mat.cc create mode 100644 cxx/node.cc create mode 100644 cxx/node.h create mode 100644 cxx/util.cc create mode 100644 package.json create mode 100644 src/cv/addon.ts create mode 100644 src/cv/common.ts create mode 100644 src/cv/consts.ts create mode 100644 src/cv/draw.ts create mode 100644 src/cv/index.ts create mode 100644 src/cv/mat.ts create mode 100644 src/cv/proc.ts create mode 100644 src/index.ts create mode 100644 src/test.ts create mode 100644 test_data/im1.jpeg create mode 100644 thirdpart/cmake-js-util.js create mode 100644 thirdpart/install.js create mode 100644 tsconfig.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ecab9ed --- /dev/null +++ b/.clang-format @@ -0,0 +1,45 @@ +# 配置参考: +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html + +# 基于那个配置文件 +BasedOnStyle: Microsoft +# 使用TAB +UseTab: Always +# TAB宽度 +TabWidth: 4 +# 行长度限制 +ColumnLimit: 0 +# 允许短的块放在同一行 +AllowShortBlocksOnASingleLine: Empty +# 是否允许短if单行 If true, if (a) return; 可以放到同一行 +AllowShortIfStatementsOnASingleLine: AllIfsAndElse +# 允许短的枚举放在同一行 +AllowShortEnumsOnASingleLine: true +# 允许短的case放在同一行 +AllowShortCaseLabelsOnASingleLine: true +# 允许短的循环保持在同一行 +AllowShortLoopsOnASingleLine: true +# 连续的空行保留几行 +MaxEmptyLinesToKeep: 2 +# 是否允许短方法单行 +AllowShortFunctionsOnASingleLine: InlineOnly +# 是否对include进行排序 +SortIncludes: Never +NamespaceIndentation: All +# case前增加空白 +IndentCaseLabels: true +# 大括号换行 +BraceWrapping: + AfterEnum: false + AfterStruct: false + SplitEmptyFunction: false + AfterClass: false + AfterControlStatement: Never + AfterFunction: true + AfterNamespace: true + AfterUnion: false + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b86c06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.vscode +/build +/dist +/node_modules +/test_data +/thirdpart/_source +/thirdpart/OpenCV +/typing +/package-lock.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..12d96da --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.10.0) +project(ai-box VERSION 0.1.0 LANGUAGES C CXX) +set(CMAKE_CXX_STANDARD 17) + +if (POLICY CMP0091) +cmake_policy(SET CMP0091 NEW) +endif (POLICY CMP0091) + +if(NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(NODE_COMMON_SOURCES) +set(NODE_ADDON_FOUND OFF) + +include_directories(cxx) + +# NodeJS +execute_process( + COMMAND node ${CMAKE_SOURCE_DIR}/thirdpart/cmake-js-util.js --include + RESULT_VARIABLE CMAKE_JS_RESULT + OUTPUT_VARIABLE CMAKE_JS_INC + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(CMAKE_JS_RESULT EQUAL 0) + execute_process( + COMMAND node ${CMAKE_SOURCE_DIR}/thirdpart/cmake-js-util.js --src + RESULT_VARIABLE CMAKE_JS_RESULT + OUTPUT_VARIABLE CMAKE_JS_SRC + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(CMAKE_JS_RESULT EQUAL 0) + execute_process( + COMMAND node ${CMAKE_SOURCE_DIR}/thirdpart/cmake-js-util.js --lib + RESULT_VARIABLE CMAKE_JS_RESULT + OUTPUT_VARIABLE CMAKE_JS_LIB + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + + # NAPI + if(CMAKE_JS_RESULT EQUAL 0) + execute_process( + COMMAND node ${CMAKE_SOURCE_DIR}/thirdpart/cmake-js-util.js --napi + RESULT_VARIABLE CMAKE_JS_RESULT + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) + endif() + if(CMAKE_JS_RESULT EQUAL 0) + message(STATUS "CMAKE_JS_INC: ${CMAKE_JS_INC}") + message(STATUS "CMAKE_JS_SRC: ${CMAKE_JS_SRC}") + message(STATUS "CMAKE_JS_LIB: ${CMAKE_JS_LIB}") + message(STATUS "NODE_ADDON_API_DIR: ${NODE_ADDON_API_DIR}") + include_directories(${CMAKE_JS_INC} ${NODE_ADDON_API_DIR}) + set(NODE_ADDON_FOUND ON) + endif() +endif() +if(NOT (CMAKE_JS_RESULT EQUAL 0)) + message(FATAL_ERROR "cmake js config failed") +endif() + + +# OpenCV +set(OpenCV_CMAKE_FILE ${CMAKE_SOURCE_DIR}/thirdpart/OpenCV/${CMAKE_BUILD_TYPE}/config.cmake) +if(EXISTS ${OpenCV_CMAKE_FILE}) + include(${OpenCV_CMAKE_FILE}) + message(STATUS "OpenCV_LIB_DIR: ${OpenCV_LIB_DIR}") + message(STATUS "OpenCV_INCLUDE_DIR: ${OpenCV_INCLUDE_DIR}") + message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}") + include_directories(${OpenCV_INCLUDE_DIRS}) + link_directories(${OpenCV_LIB_DIR}) +else() + message(FATAL_ERROR "OpenCV config failed") +endif() + +# 构建目标 +add_library(cv SHARED ${CMAKE_JS_SRC} + cxx/node.cc + cxx/mat.cc + cxx/util.cc +) +target_link_libraries(cv ${CMAKE_JS_LIB} ${OpenCV_LIBS}) +set_target_properties(cv PROPERTIES PREFIX "" SUFFIX ".node") + + +if(MSVC AND NODE_ADDON_FOUND) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..656f700 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# @yizhi/opencv + +简单的OpenCV封装 + +## 编译源码 +1. 依赖 + 1. python3 + 1. cmake + 1. ninja + 1. c++编译器(gcc,clang,Visual Studio ...) + +1. 编译OpenCV +```bash +node thirdpart/install.js --with-opencv +``` +1. 编译本模块 +```bash +cmake -B build -G Ninja . -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release +``` + +## 安装 +```bash +npm install @yizhi/opencv +``` + +## 使用 +```typescript +//导入opencv +import cv from '@yizhi/opencv'; + +//配置addon路径 +cv.config("ADDON_PATH", "/path/to/cv.node"); + +//正常使用 +const im = cv.imread("/path/to/input"); +cv.resize(im, 640, 640); +cv.imwrite("/path/to/output", im); +``` diff --git a/cxx/mat.cc b/cxx/mat.cc new file mode 100644 index 0000000..fd89313 --- /dev/null +++ b/cxx/mat.cc @@ -0,0 +1,450 @@ +#include "node.h" + +using namespace Napi; + +#define MAT_INSTANCE_METHOD(method) InstanceMethod<&CVMat::method>(#method, static_cast(napi_writable | napi_configurable)) +#define MAT_STATIC_METHOD(method) StaticMethod<&CVMat::method>(#method, static_cast(napi_writable | napi_configurable)) + +static FunctionReference *constructor = nullptr; + +class CVMat : public ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports) + { + Function func = DefineClass(env, "Mat", { + MAT_STATIC_METHOD(ImRead), + MAT_STATIC_METHOD(ImDecode), + MAT_STATIC_METHOD(ImWrite), + MAT_STATIC_METHOD(ImEncode), + + MAT_STATIC_METHOD(Crop), + MAT_STATIC_METHOD(Resize), + MAT_STATIC_METHOD(WarpAffine), + MAT_STATIC_METHOD(Blur), + MAT_STATIC_METHOD(CopyMakeBorder), + MAT_STATIC_METHOD(Flip), + MAT_STATIC_METHOD(GetRotationMatrix2D), + + MAT_STATIC_METHOD(Rectangle), + MAT_STATIC_METHOD(Circle), + MAT_STATIC_METHOD(Line), + MAT_STATIC_METHOD(Ellipse), + MAT_STATIC_METHOD(Polylines), + MAT_STATIC_METHOD(FillPoly), + MAT_STATIC_METHOD(FillConvexPoly), + MAT_STATIC_METHOD(DrawMarker), + MAT_STATIC_METHOD(PutText), + + MAT_INSTANCE_METHOD(IsEmpty), + MAT_INSTANCE_METHOD(GetCols), + MAT_INSTANCE_METHOD(GetRows), + MAT_INSTANCE_METHOD(GetType), + MAT_INSTANCE_METHOD(GetDepth), + MAT_INSTANCE_METHOD(GetChannels), + MAT_INSTANCE_METHOD(GetFlags), + MAT_INSTANCE_METHOD(GetDims), + MAT_INSTANCE_METHOD(GetIsContinuous), + MAT_INSTANCE_METHOD(GetIsSubmatrix), + MAT_INSTANCE_METHOD(GetElemSize), + MAT_INSTANCE_METHOD(GetElemSize1), + MAT_INSTANCE_METHOD(GetTotal), + MAT_INSTANCE_METHOD(GetTotalWithDim), + MAT_INSTANCE_METHOD(GetSize), + + MAT_INSTANCE_METHOD(Col), + MAT_INSTANCE_METHOD(ColRange), + MAT_INSTANCE_METHOD(Row), + MAT_INSTANCE_METHOD(RowRange), + MAT_INSTANCE_METHOD(Diag), + + MAT_INSTANCE_METHOD(Clone), + + }); + constructor = new FunctionReference(); + *constructor = Napi::Persistent(func); + exports.Set("Mat", func); + env.SetInstanceData(constructor); + return exports; + } + + CVMat(const CallbackInfo &info) + : ObjectWrap(info) + { + if (info.Length() >= 2) { + auto sizesArray = info[0].As(); + std::vector sizes(sizesArray.Length()); + for (auto i = 0; i < sizes.size(); ++i) sizes[i] = sizesArray.Get(i).As().Int32Value(); + auto type = info[1].As().Int32Value(); + // 使用sizes和type初始化 + if (info.Length() < 3) mat_ = cv::Mat(sizes, type); + // 使用数据初始化 + else if (info[2].IsTypedArray()) { + auto dataArray = info[2].As(); + auto data = static_cast(dataArray.ArrayBuffer().Data()) + dataArray.ByteOffset(); + mat_ = cv::Mat(sizes, type, data); + } + // 其他 TODO + else mat_ = cv::Mat(sizes, type); + } + } + + + static Napi::Value ImRead(const Napi::CallbackInfo &info) + { + auto filename = info[0].As().Utf8Value(); + int flag = (info.Length() >= 2) ? info[1].As().Int32Value() : cv::IMREAD_COLOR_BGR; + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = cv::imread(filename, flag); }); + } + static Napi::Value ImDecode(const Napi::CallbackInfo &info) + { + auto arr = info[0].As(); + auto data = static_cast(arr.ArrayBuffer().Data()) + arr.ByteOffset(); + std::vector buffer(data, data + arr.ByteLength()); + int flag = (info.Length() >= 2) ? info[1].As().Int32Value() : cv::IMREAD_COLOR_BGR; + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = cv::imdecode(buffer, flag); }); + } + static Napi::Value ImWrite(const Napi::CallbackInfo &info) + { + auto &mat = GetMat(info[0].As()); + auto filename = info[1].As().Utf8Value(); + std::vector params; + if (info[2].IsArray()) { + auto paramArray = info[2].As(); + params.resize(paramArray.Length()); + for (auto i = 0; i < params.size(); ++i) params[i] = paramArray.Get(i).As().Int32Value(); + } + auto res = cv::imwrite(filename, mat.mat_, params); + return Boolean::New(info.Env(), res); + } + static Napi::Value ImEncode(const Napi::CallbackInfo &info) + { + auto &mat = GetMat(info[0].As()); + auto extname = info[1].As().Utf8Value(); + std::vector params; + if (info[2].IsArray()) { + auto paramArray = info[2].As(); + params.resize(paramArray.Length()); + for (auto i = 0; i < params.size(); ++i) params[i] = paramArray.Get(i).As().Int32Value(); + } + std::vector buf; + if (!cv::imencode(extname, mat.mat_, buf, params)) return info.Env().Undefined(); + auto arrayBuffer = ArrayBuffer::New(info.Env(), buf.size()); + auto bufferPtr = static_cast(arrayBuffer.Data()); + for (auto ch : buf) { + *bufferPtr = ch; + bufferPtr++; + } + return arrayBuffer; + } + + + Napi::Value IsEmpty(const Napi::CallbackInfo &info) { return Boolean::New(info.Env(), mat_.empty()); } + Napi::Value GetCols(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.cols); } + Napi::Value GetRows(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.rows); } + Napi::Value GetType(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.type()); } + Napi::Value GetDepth(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.depth()); } + Napi::Value GetChannels(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.channels()); } + Napi::Value GetFlags(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.flags); } + Napi::Value GetDims(const Napi::CallbackInfo &info) { return Number::New(info.Env(), mat_.dims); } + Napi::Value GetIsContinuous(const Napi::CallbackInfo &info) { return Boolean::New(info.Env(), mat_.isContinuous()); } + Napi::Value GetIsSubmatrix(const Napi::CallbackInfo &info) { return Boolean::New(info.Env(), mat_.isSubmatrix()); } + Napi::Value GetElemSize(const Napi::CallbackInfo &info) { return Number::New(info.Env(), static_cast(mat_.elemSize())); } + Napi::Value GetElemSize1(const Napi::CallbackInfo &info) { return Number::New(info.Env(), static_cast(mat_.elemSize1())); } + Napi::Value GetTotal(const Napi::CallbackInfo &info) { return Number::New(info.Env(), static_cast(mat_.total())); } + Napi::Value GetTotalWithDim(const Napi::CallbackInfo &info) { return Number::New(info.Env(), static_cast(mat_.total(info[0].As().Int32Value(), info[1].As().Int32Value()))); } + Napi::Value GetSize(const Napi::CallbackInfo &info) + { + auto ret = Array::New(info.Env(), mat_.dims); + auto &size = mat_.size; + for (int i = 0; i < mat_.dims; ++i) ret.Set(i, size[i]); + return ret; + } + + + Napi::Value Col(const Napi::CallbackInfo &info) + { + auto col = info[0].As().Int32Value(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.col(col); }); + } + Napi::Value ColRange(const Napi::CallbackInfo &info) + { + auto start = info[0].As().Int32Value(); + auto end = info[1].As().Int32Value(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.colRange(start, end); }); + } + Napi::Value Row(const Napi::CallbackInfo &info) + { + auto row = info[0].As().Int32Value(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.row(row); }); + } + Napi::Value RowRange(const Napi::CallbackInfo &info) + { + auto start = info[0].As().Int32Value(); + auto end = info[1].As().Int32Value(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.rowRange(start, end); }); + } + Napi::Value Diag(const Napi::CallbackInfo &info) + { + auto d = info[0].As().Int32Value(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.diag(d); }); + } + + + Napi::Value Clone(const Napi::CallbackInfo &info) + { + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.clone(); }); + } + + + static Napi::Value Crop(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto rangeArray = info[2].As(); + std::vector ranges(rangeArray.Length()); + for (int i = 0; i < ranges.size(); ++i) { + auto item = rangeArray.Get(i).As(); + auto start = item.Get("start").As().Int32Value(); + auto end = item.Get("end").As().Int32Value(); + ranges[i] = cv::Range(start, end); + } + dst.mat_ = src.mat_(ranges); + return info[1]; + } + static Napi::Value Resize(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto width = info[2].As().Int32Value(); + auto height = info[3].As().Int32Value(); + auto fx = info[4].As().DoubleValue(); + auto fy = info[5].As().DoubleValue(); + auto interpolation = info[6].As().Int32Value(); + cv::resize(src.mat_, dst.mat_, cv::Size(width, height), fx, fy, interpolation); + return info[1]; + } + static Napi::Value WarpAffine(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto &transformMat = GetMat(info[2].As()); + auto dwidth = info[3].As().Int32Value(); + auto dheight = info[4].As().Int32Value(); + auto flags = info[5].As().Int32Value(); + auto borderMode = info[6].As().Int32Value(); + cv::warpAffine(src.mat_, dst.mat_, transformMat.mat_, cv::Size(dwidth, dheight), flags, borderMode); + return info[1]; + } + static Napi::Value Blur(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto kwidth = info[2].As().Int32Value(); + auto kheight = info[3].As().Int32Value(); + auto anchorX = info[4].As().Int32Value(); + auto anchorY = info[5].As().Int32Value(); + auto borderType = info[6].As().Int32Value(); + cv::blur(src.mat_, dst.mat_, cv::Size(kwidth, kheight), cv::Point(anchorX, anchorY), borderType); + return info[1]; + } + static Napi::Value CopyMakeBorder(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto top = info[2].As().Int32Value(); + auto bottom = info[3].As().Int32Value(); + auto left = info[4].As().Int32Value(); + auto right = info[5].As().Int32Value(); + auto borderType = info[6].As().Int32Value(); + cv::copyMakeBorder(src.mat_, dst.mat_, top, bottom, left, right, borderType); + return info[1]; + } + static Napi::Value Flip(const Napi::CallbackInfo &info) + { + auto &src = GetMat(info[0].As()); + auto &dst = GetMat(info[1].As()); + auto flipCode = info[2].As().Int32Value(); + cv::flip(src.mat_, dst.mat_, flipCode); + return info[1]; + } + static Napi::Value GetRotationMatrix2D(const Napi::CallbackInfo &info) + { + auto centerX = info[0].As().FloatValue(); + auto centerY = info[1].As().FloatValue(); + auto angle = info[2].As().DoubleValue(); + auto scale = info[3].As().DoubleValue(); + return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = cv::getRotationMatrix2D(cv::Point2f(centerX, centerY), angle, scale); }); + } + + + static Napi::Value Rectangle(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto x1 = info[1].As().Int32Value(); + auto y1 = info[2].As().Int32Value(); + auto x2 = info[3].As().Int32Value(); + auto y2 = info[4].As().Int32Value(); + auto b = info[5].As().DoubleValue(); + auto g = info[6].As().DoubleValue(); + auto r = info[7].As().DoubleValue(); + auto thickness = info[8].As().Int32Value(); + auto lineType = info[9].As().Int32Value(); + auto shift = info[10].As().Int32Value(); + cv::rectangle(img.mat_, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(b, g, r), thickness, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value Circle(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto x = info[1].As().Int32Value(); + auto y = info[2].As().Int32Value(); + auto radius = info[3].As().Int32Value(); + auto b = info[4].As().DoubleValue(); + auto g = info[5].As().DoubleValue(); + auto r = info[6].As().DoubleValue(); + auto thickness = info[7].As().Int32Value(); + auto lineType = info[8].As().Int32Value(); + auto shift = info[9].As().Int32Value(); + cv::circle(img.mat_, cv::Point(x, y), radius, cv::Scalar(b, g, r), thickness, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value Line(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto x1 = info[1].As().Int32Value(); + auto y1 = info[2].As().Int32Value(); + auto x2 = info[3].As().Int32Value(); + auto y2 = info[4].As().Int32Value(); + auto b = info[5].As().DoubleValue(); + auto g = info[6].As().DoubleValue(); + auto r = info[7].As().DoubleValue(); + auto thickness = info[8].As().Int32Value(); + auto lineType = info[9].As().Int32Value(); + auto shift = info[10].As().Int32Value(); + cv::line(img.mat_, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(b, g, r), thickness, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value Ellipse(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto x = info[1].As().Int32Value(); + auto y = info[2].As().Int32Value(); + auto width = info[3].As().Int32Value(); + auto height = info[4].As().Int32Value(); + auto angle = info[5].As().DoubleValue(); + auto startAngle = info[6].As().DoubleValue(); + auto endAngle = info[7].As().DoubleValue(); + auto b = info[8].As().DoubleValue(); + auto g = info[9].As().DoubleValue(); + auto r = info[10].As().DoubleValue(); + auto thickness = info[11].As().Int32Value(); + auto lineType = info[12].As().Int32Value(); + auto shift = info[13].As().Int32Value(); + cv::ellipse(img.mat_, cv::Point(x, y), cv::Size(width, height), angle, startAngle, endAngle, cv::Scalar(b, g, r), thickness, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value Polylines(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto points_ = info[1].As(); + std::vector points(points_.Length()); + for (uint32_t i = 0; i < points_.Length(); i++) { + auto pt = points_.Get(i).As(); + points[i] = cv::Point(pt.Get(0U).As().Int32Value(), pt.Get(1U).As().Int32Value()); + } + auto isClosed = info[2].As().Value(); + auto b = info[3].As().DoubleValue(); + auto g = info[4].As().DoubleValue(); + auto r = info[5].As().DoubleValue(); + auto thickness = info[6].As().Int32Value(); + auto lineType = info[7].As().Int32Value(); + auto shift = info[8].As().Int32Value(); + cv::polylines(img.mat_, points, isClosed, cv::Scalar(b, g, r), thickness, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value FillPoly(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto points_ = info[1].As(); + std::vector points(points_.Length()); + for (uint32_t i = 0; i < points_.Length(); i++) { + auto pt = points_.Get(i).As(); + points[i] = cv::Point(pt.Get(0U).As().Int32Value(), pt.Get(1U).As().Int32Value()); + } + auto b = info[2].As().DoubleValue(); + auto g = info[3].As().DoubleValue(); + auto r = info[4].As().DoubleValue(); + auto lineType = info[5].As().Int32Value(); + auto shift = info[6].As().Int32Value(); + cv::fillPoly(img.mat_, points, cv::Scalar(b, g, r), lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value FillConvexPoly(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto points_ = info[1].As(); + std::vector points(points_.Length()); + for (uint32_t i = 0; i < points_.Length(); i++) { + auto pt = points_.Get(i).As(); + points[i] = cv::Point(pt.Get(0U).As().Int32Value(), pt.Get(1U).As().Int32Value()); + } + auto b = info[2].As().DoubleValue(); + auto g = info[3].As().DoubleValue(); + auto r = info[4].As().DoubleValue(); + auto lineType = info[5].As().Int32Value(); + auto shift = info[6].As().Int32Value(); + cv::fillConvexPoly(img.mat_, points, cv::Scalar(b, g, r), lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value DrawMarker(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto x = info[1].As().Int32Value(); + auto y = info[2].As().Int32Value(); + auto b = info[3].As().DoubleValue(); + auto g = info[4].As().DoubleValue(); + auto r = info[5].As().DoubleValue(); + auto markerType = info[6].As().Int32Value(); + auto markerSize = info[7].As().Int32Value(); + auto lineType = info[8].As().Int32Value(); + auto shift = info[9].As().Int32Value(); + cv::drawMarker(img.mat_, cv::Point(x, y), cv::Scalar(b, g, r), markerType, markerSize, lineType, shift); + return info.Env().Undefined(); + } + static Napi::Value PutText(const Napi::CallbackInfo &info) + { + auto &img = GetMat(info[0].As()); + auto text = info[1].As().Utf8Value(); + auto x = info[2].As().Int32Value(); + auto y = info[3].As().Int32Value(); + auto fontFace = info[4].As().Int32Value(); + auto fontScale = info[5].As().DoubleValue(); + auto b = info[6].As().DoubleValue(); + auto g = info[7].As().DoubleValue(); + auto r = info[8].As().DoubleValue(); + auto thickness = info[9].As().Int32Value(); + auto lineType = info[10].As().Int32Value(); + auto shift = info[11].As().Int32Value(); + auto bottomLeftOrigin = info[12].As().Value(); + cv::putText(img.mat_, text, cv::Point(x, y), fontFace, fontScale, cv::Scalar(b, g, r), thickness, lineType, bottomLeftOrigin); + return info.Env().Undefined(); + } + + private: + inline static Napi::Object EmptyMat(Napi::Env env) { return constructor->New({}).As(); } + inline static CVMat &GetMat(Napi::Object obj) { return *ObjectWrap::Unwrap(obj); } + inline static Napi::Object CreateMat(Napi::Env env, std::function callback) + { + auto obj = EmptyMat(env); + callback(GetMat(obj)); + return obj; + } + + private: + cv::Mat mat_; +}; + +void InitMatAPI(Napi::Env env, Napi::Object exports) +{ + CVMat::Init(env, exports); +} \ No newline at end of file diff --git a/cxx/node.cc b/cxx/node.cc new file mode 100644 index 0000000..c3087eb --- /dev/null +++ b/cxx/node.cc @@ -0,0 +1,11 @@ + +#include "node.h" + + +static Napi::Object Init(Napi::Env env, Napi::Object exports) +{ + InitMatAPI(env, exports); + InitProcAPI(env, exports); + return exports; +} +NODE_API_MODULE(addon, Init); \ No newline at end of file diff --git a/cxx/node.h b/cxx/node.h new file mode 100644 index 0000000..1913d0f --- /dev/null +++ b/cxx/node.h @@ -0,0 +1,10 @@ +#ifndef __CV_NODE_H__ +#define __CV_NODE_H__ + +#include +#include + +void InitMatAPI(Napi::Env env, Napi::Object exports); +void InitProcAPI(Napi::Env env, Napi::Object exports); + +#endif diff --git a/cxx/util.cc b/cxx/util.cc new file mode 100644 index 0000000..913333f --- /dev/null +++ b/cxx/util.cc @@ -0,0 +1,35 @@ +#include "node.h" +using namespace Napi; + +#define ADD_PROC_FUNCTION(name) obj.Set(#name, Napi::Function::New(env)); +#define DEFINE_FUNCTION(name) Napi::Value name(Napi::CallbackInfo const &info) + +DEFINE_FUNCTION(GetTextSize) +{ + auto text = info[0].As().Utf8Value(); + auto fontFace = info[1].As().Int32Value(); + auto fontScale = info[2].As().DoubleValue(); + auto thickness = info[3].As().Int32Value(); + int baseLine; + auto size = cv::getTextSize(text, fontFace, fontScale, thickness, &baseLine); + auto res = Object::New(info.Env()); + res.Set("width", Number::New(info.Env(), size.width)); + res.Set("height", Number::New(info.Env(), size.height)); + res.Set("baseLine", Number::New(info.Env(), baseLine)); + return res; +} + +DEFINE_FUNCTION(GetFontScaleFromHeight) +{ + auto fontFace = info[0].As().Int32Value(); + auto pixelHeight = info[1].As().Int32Value(); + auto thickness = info[2].As().Int32Value(); + return Number::New(info.Env(), cv::getFontScaleFromHeight(fontFace, pixelHeight, thickness)); +} + +void InitProcAPI(Napi::Env env, Napi::Object exports) +{ + auto obj = Object::New(env); + ADD_PROC_FUNCTION(GetTextSize); + exports.Set("util", obj); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..59a9ca5 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "@yizhi/cv", + "version": "1.0.0", + "main": "dist/index.js", + "types": "typing/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "tsc -w --inlineSourceMap" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/node": "^22.13.5", + "cmake-js": "^7.3.0", + "node-addon-api": "^8.3.1" + } +} \ No newline at end of file diff --git a/src/cv/addon.ts b/src/cv/addon.ts new file mode 100644 index 0000000..178e2df --- /dev/null +++ b/src/cv/addon.ts @@ -0,0 +1,19 @@ +import { LINE_8, MARKER_CROSS } from "./consts"; + +export const config = { + ADDON_PATH: "../../build/cv.node", + DEFAULT_LINE_TYPE: LINE_8, + DEFAULT_THICKNESS: 1, + DEFAULT_MARKER: MARKER_CROSS, +}; + +export function getAddon() { + return require(config.ADDON_PATH); +} + +export function getConfig(name: N): typeof config[N] { return config[name]; } +export function setConfig(name: N, value: typeof config[N]) { config[name] = value; } + +export function CVMat() { return getAddon().Mat; } + +export function CVUtil() { return getAddon().util; } diff --git a/src/cv/common.ts b/src/cv/common.ts new file mode 100644 index 0000000..44caade --- /dev/null +++ b/src/cv/common.ts @@ -0,0 +1,14 @@ +import { getAddon, getConfig } from "./addon"; +import { Mat } from "./mat"; + +export type TypedArray = Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | BigUint64Array | BigInt64Array | Float32Array | Float64Array + +export function FromCV(mat: any): Mat { return new (Mat as any)(mat); } +export function M(mat: Mat) { return (mat as any).__mat__; } +export function C(name: Parameters[0]) { return getConfig(name); } + +export function resolveArgs>(args: any[], checker: boolean | (() => boolean)): R { + if (typeof checker === "function") checker = checker(); + if (checker) return args as R; + return [args[0], new Mat(), ...args.slice(1)] as R; +} diff --git a/src/cv/consts.ts b/src/cv/consts.ts new file mode 100644 index 0000000..3b98369 --- /dev/null +++ b/src/cv/consts.ts @@ -0,0 +1,166 @@ +export const IMREAD_UNCHANGED = -1; +export const IMREAD_GRAYSCALE = 0; +export const IMREAD_COLOR_BGR = 1; +export const IMREAD_COLOR = 1; +export const IMREAD_ANYDEPTH = 2; +export const IMREAD_ANYCOLOR = 4; +export const IMREAD_LOAD_GDAL = 8; +export const IMREAD_REDUCED_GRAYSCALE_2 = 16; +export const IMREAD_REDUCED_COLOR_2 = 17; +export const IMREAD_REDUCED_GRAYSCALE_4 = 32; +export const IMREAD_REDUCED_COLOR_4 = 33; +export const IMREAD_REDUCED_GRAYSCALE_8 = 64; +export const IMREAD_REDUCED_COLOR_8 = 65; +export const IMREAD_IGNORE_ORIENTATION = 128; +export const IMREAD_COLOR_RGB = 256; + +export const CV_8U = 0; +export const CV_8S = 1; +export const CV_16U = 2; +export const CV_16S = 3; +export const CV_32S = 4; +export const CV_32F = 5; +export const CV_64F = 6; +export const CV_16F = 7; + +const CV_CN_MAX = 512 +const CV_CN_SHIFT = 3 +const CV_DEPTH_MAX = (1 << CV_CN_SHIFT) +const CV_MAT_DEPTH_MASK = (CV_DEPTH_MAX - 1) +const CV_MAT_DEPTH = (flags: number) => ((flags) & CV_MAT_DEPTH_MASK) +const CV_MAKETYPE = (depth: number, cn: number) => (CV_MAT_DEPTH(depth) + (((cn) - 1) << CV_CN_SHIFT)) + +export const CV_8UC1 = CV_MAKETYPE(CV_8U, 1); +export const CV_8UC2 = CV_MAKETYPE(CV_8U, 2); +export const CV_8UC3 = CV_MAKETYPE(CV_8U, 3); +export const CV_8UC4 = CV_MAKETYPE(CV_8U, 4); +export const CV_8UC = (n: number) => CV_MAKETYPE(CV_8U, (n)); + +export const CV_8SC1 = CV_MAKETYPE(CV_8S, 1); +export const CV_8SC2 = CV_MAKETYPE(CV_8S, 2); +export const CV_8SC3 = CV_MAKETYPE(CV_8S, 3); +export const CV_8SC4 = CV_MAKETYPE(CV_8S, 4); +export const CV_8SC = (n: number) => CV_MAKETYPE(CV_8S, (n)); + +export const CV_16UC1 = CV_MAKETYPE(CV_16U, 1); +export const CV_16UC2 = CV_MAKETYPE(CV_16U, 2); +export const CV_16UC3 = CV_MAKETYPE(CV_16U, 3); +export const CV_16UC4 = CV_MAKETYPE(CV_16U, 4); +export const CV_16UC = (n: number) => CV_MAKETYPE(CV_16U, (n)); + +export const CV_16SC1 = CV_MAKETYPE(CV_16S, 1); +export const CV_16SC2 = CV_MAKETYPE(CV_16S, 2); +export const CV_16SC3 = CV_MAKETYPE(CV_16S, 3); +export const CV_16SC4 = CV_MAKETYPE(CV_16S, 4); +export const CV_16SC = (n: number) => CV_MAKETYPE(CV_16S, (n)); + +export const CV_32SC1 = CV_MAKETYPE(CV_32S, 1); +export const CV_32SC2 = CV_MAKETYPE(CV_32S, 2); +export const CV_32SC3 = CV_MAKETYPE(CV_32S, 3); +export const CV_32SC4 = CV_MAKETYPE(CV_32S, 4); +export const CV_32SC = (n: number) => CV_MAKETYPE(CV_32S, (n)); + +export const CV_32FC1 = CV_MAKETYPE(CV_32F, 1); +export const CV_32FC2 = CV_MAKETYPE(CV_32F, 2); +export const CV_32FC3 = CV_MAKETYPE(CV_32F, 3); +export const CV_32FC4 = CV_MAKETYPE(CV_32F, 4); +export const CV_32FC = (n: number) => CV_MAKETYPE(CV_32F, (n)); + +export const CV_64FC1 = CV_MAKETYPE(CV_64F, 1); +export const CV_64FC2 = CV_MAKETYPE(CV_64F, 2); +export const CV_64FC3 = CV_MAKETYPE(CV_64F, 3); +export const CV_64FC4 = CV_MAKETYPE(CV_64F, 4); +export const CV_64FC = (n: number) => CV_MAKETYPE(CV_64F, (n)); + +export const CV_16FC1 = CV_MAKETYPE(CV_16F, 1); +export const CV_16FC2 = CV_MAKETYPE(CV_16F, 2); +export const CV_16FC3 = CV_MAKETYPE(CV_16F, 3); +export const CV_16FC4 = CV_MAKETYPE(CV_16F, 4); +export const CV_16FC = (n: number) => CV_MAKETYPE(CV_16F, (n)); + + +export const INTER_NEAREST = 0; +export const INTER_LINEAR = 1; +export const INTER_CUBIC = 2; +export const INTER_AREA = 3; +export const INTER_LANCZOS4 = 4; +export const INTER_LINEAR_EXACT = 5; +export const INTER_NEAREST_EXACT = 6; +export const INTER_MAX = 7; +export const WARP_FILL_OUTLIERS = 8; +export const WARP_INVERSE_MAP = 16; +export const WARP_RELATIVE_MAP = 32; + + +export const BORDER_CONSTANT = 0; +export const BORDER_REPLICATE = 1; +export const BORDER_REFLECT = 2; +export const BORDER_WRAP = 3; +export const BORDER_REFLECT_101 = 4; +export const BORDER_TRANSPARENT = 5; +export const BORDER_REFLECT101 = BORDER_REFLECT_101; +export const BORDER_DEFAULT = BORDER_REFLECT_101; +export const BORDER_ISOLATED = 16; + +export const IMWRITE_JPEG_QUALITY = 1; +export const IMWRITE_JPEG_PROGRESSIVE = 2; +export const IMWRITE_JPEG_OPTIMIZE = 3; +export const IMWRITE_JPEG_RST_INTERVAL = 4; +export const IMWRITE_JPEG_LUMA_QUALITY = 5; +export const IMWRITE_JPEG_CHROMA_QUALITY = 6; +export const IMWRITE_JPEG_SAMPLING_FACTOR = 7; +export const IMWRITE_PNG_COMPRESSION = 16; +export const IMWRITE_PNG_STRATEGY = 17; +export const IMWRITE_PNG_BILEVEL = 18; +export const IMWRITE_PXM_BINARY = 32; +export const IMWRITE_EXR_TYPE = (3 << 4) + 0; +export const IMWRITE_EXR_COMPRESSION = (3 << 4) + 1; +export const IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2; +export const IMWRITE_WEBP_QUALITY = 64; +export const IMWRITE_HDR_COMPRESSION = (5 << 4) + 0; +export const IMWRITE_PAM_TUPLETYPE = 128; +export const IMWRITE_TIFF_RESUNIT = 256; +export const IMWRITE_TIFF_XDPI = 257; +export const IMWRITE_TIFF_YDPI = 258; +export const IMWRITE_TIFF_COMPRESSION = 259; +export const IMWRITE_TIFF_ROWSPERSTRIP = 278; +export const IMWRITE_TIFF_PREDICTOR = 317; +export const IMWRITE_JPEG2000_COMPRESSION_X1000 = 272; +export const IMWRITE_AVIF_QUALITY = 512; +export const IMWRITE_AVIF_DEPTH = 513; +export const IMWRITE_AVIF_SPEED = 514; +export const IMWRITE_JPEGXL_QUALITY = 640; +export const IMWRITE_JPEGXL_EFFORT = 641; +export const IMWRITE_JPEGXL_DISTANCE = 642; +export const IMWRITE_JPEGXL_DECODING_SPEED = 643; +export const IMWRITE_GIF_LOOP = 1024; +export const IMWRITE_GIF_SPEED = 1025; +export const IMWRITE_GIF_QUALITY = 1026; +export const IMWRITE_GIF_DITHER = 1027; +export const IMWRITE_GIF_TRANSPARENCY = 1028; +export const IMWRITE_GIF_COLORTABLE = 1029; + +export const FILLED = -1; +export const LINE_4 = 4; +export const LINE_8 = 8; +export const LINE_AA = 16; + + +export const FONT_HERSHEY_SIMPLEX = 0; +export const FONT_HERSHEY_PLAIN = 1; +export const FONT_HERSHEY_DUPLEX = 2; +export const FONT_HERSHEY_COMPLEX = 3; +export const FONT_HERSHEY_TRIPLEX = 4; +export const FONT_HERSHEY_COMPLEX_SMALL = 5; +export const FONT_HERSHEY_SCRIPT_SIMPLEX = 6; +export const FONT_HERSHEY_SCRIPT_COMPLEX = 7; +export const FONT_ITALIC = 16; + + +export const MARKER_CROSS = 0; +export const MARKER_TILTED_CROSS = 1; +export const MARKER_STAR = 2; +export const MARKER_DIAMOND = 3; +export const MARKER_SQUARE = 4; +export const MARKER_TRIANGLE_UP = 5; +export const MARKER_TRIANGLE_DOWN = 6; diff --git a/src/cv/draw.ts b/src/cv/draw.ts new file mode 100644 index 0000000..fdaa35c --- /dev/null +++ b/src/cv/draw.ts @@ -0,0 +1,104 @@ +import { CVMat, CVUtil } from "./addon"; +import { C, M } from "./common"; +import { Mat } from "./mat"; + +type Color = [b: number, g: number, r: number] | { r: number, g: number, b: number }; +type Point = [x: number, y: number] | { x: number, y: number }; +type Size = [width: number, height: number] | { width: number, height: number }; +type Rect = [x: number, y: number, width: number, height: number] | { x: number, y: number, width: number, height: number }; + +function __checkType(val: any, props: string[]) { + if (!val) return false; + if (val instanceof Array) return val.length === props.length && val.every(i => typeof i === "number"); + return Object.keys(props).length === props.length && props.every(p => typeof val?.[p] === "number"); +} +function __typeToArray(val: any, props: string[]) { + if (val instanceof Array) return val as R; + return props.map(p => val[p]) as R; +} + +function isColor(val: any): val is Color { return __checkType(val, ["r", "g", "b"]); } +function isPoint(val: any): val is Point { return __checkType(val, ["x", "y"]); } +function isSize(val: any): val is Size { return __checkType(val, ["width", "height"]); } +function isRect(val: any): val is Rect { return __checkType(val, ["x", "y", "width", "height"]); } + +function getColor(color: Color) { return __typeToArray<[number, number, number]>(color, ["b", "g", "r"]); } +function getPoint(point: Point) { return __typeToArray<[number, number]>(point, ["x", "y"]); } +function getSize(point: Size) { return __typeToArray<[number, number]>(point, ["width", "height"]); } +function getRectPoints(rect: Rect): [number, number, number, number] { + const [x, y, width, height] = __typeToArray(rect, ["x", "y", "width", "height"]); + return [x, y, x + width, y + height]; +} + +export function rectangle(img: Mat, rect: Rect, color: Color, thickness?: number, lineType?: number, shift?: number): void +export function rectangle(img: Mat, pt1: Point, pt2: Point, color: Color, thickness?: number, lineType?: number, shift?: number): void +export function rectangle(img: Mat, x1: number, y1: number, x2: number, y2: number, b: number, g: number, r: number, thickness?: number, lineType?: number, shift?: number): void +export function rectangle(img: Mat, ...rest: any[]) { + if (isPoint(rest[0])) rectangle(img, ...getPoint(rest[0]), ...getPoint(rest[1]), ...getColor(rest[2]), ...rest.slice(3)); + else if (isRect(rest[0])) rectangle(img, ...getRectPoints(rest[0]), ...getColor(rest[1]), ...rest.slice(2)); + else CVMat().Rectangle(M(img), rest[0], rest[1], rest[2], rest[3], rest[4], rest[5], rest[6], rest[7] ?? C("DEFAULT_THICKNESS"), rest[8] ?? C("DEFAULT_LINE_TYPE"), rest[9] ?? 0); +} + +export function circle(img: Mat, center: Point, radius: number, color: Color, thickness?: number, lineType?: number, shift?: number): void +export function circle(img: Mat, x: number, y: number, radius: number, b: number, g: number, r: number, thickness?: number, lineType?: number, shift?: number): void +export function circle(img: Mat, ...rest: any[]) { + if (isPoint(rest[0])) circle(img, ...getPoint(rest[0]), rest[1], ...getColor(rest[2]), ...rest.slice(3)); + else CVMat().Circle(M(img), rest[0], rest[1], rest[2], rest[3], rest[4], rest[5], rest[6] ?? C("DEFAULT_THICKNESS"), rest[7] ?? C("DEFAULT_LINE_TYPE"), rest[8] ?? 0); +} + +export function line(img: Mat, pt1: Point, pt2: Point, color: Color, thickness?: number, lineType?: number, shift?: number): void +export function line(img: Mat, x1: number, y1: number, x2: number, y2: number, b: number, g: number, r: number, thickness?: number, lineType?: number, shift?: number): void +export function line(img: Mat, ...rest: any[]) { + if (isPoint(rest[0])) line(img, ...getPoint(rest[0]), ...getPoint(rest[1]), ...getColor(rest[2]), ...rest.slice(3)); + else CVMat().Line(M(img), rest[0], rest[1], rest[2], rest[3], rest[4], rest[5], rest[6], rest[7] ?? C("DEFAULT_THICKNESS"), rest[8] ?? C("DEFAULT_LINE_TYPE"), rest[9] ?? 0); +} + +export function ellipse(img: Mat, center: Point, size: Size, angle: number, startAngle: number, endAngle: number, color: Color, thickness?: number, lineType?: number, shift?: number): void +export function ellipse(img: Mat, centerX: number, centerY: number, width: number, height: number, angle: number, startAngle: number, endAngle: number, b: number, g: number, r: number, thickness?: number, lineType?: number, shift?: number): void +export function ellipse(img: Mat, ...rest: any[]) { + if (isPoint(rest[0])) ellipse(img, ...getPoint(rest[0]), ...getSize(rest[1]), rest[2], rest[3], rest[4], ...getColor(rest[5]), ...rest.slice(6)); + else CVMat().Ellipse(M(img), rest[0], rest[1], rest[2], rest[3], rest[4], rest[5], rest[6], rest[7], rest[8], rest[9], rest[10] ?? C("DEFAULT_THICKNESS"), rest[11] ?? C("DEFAULT_LINE_TYPE"), rest[12] ?? 0); +} + +export function polylines(img: Mat, points: Point[], isClosed: boolean, color: Color, thickness?: number, lineType?: number, shift?: number): void; +export function polylines(img: Mat, points: Point[], isClosed: boolean, b: number, g: number, r: number, thickness?: number, lineType?: number, shift?: number): void; +export function polylines(img: Mat, points: Point[], isClosed: boolean, ...rest: any[]) { + if (isColor(rest[0])) polylines(img, points, isClosed, ...getColor(rest[0]), rest[1], rest[2], rest[3]); + else CVMat().Polylines(M(img), points.map(p => getPoint(p)), isClosed, rest[0], rest[1], rest[2], rest[3] ?? C("DEFAULT_THICKNESS"), rest[4] ?? C("DEFAULT_LINE_TYPE"), rest[5] ?? 0); +} + +export function fillPoly(img: Mat, points: Point[], color: Color, lineType?: number, shift?: number): void; +export function fillPoly(img: Mat, points: Point[], b: number, g: number, r: number, lineType?: number, shift?: number): void; +export function fillPoly(img: Mat, points: Point[], ...rest: any[]) { + if (isColor(rest[0])) fillPoly(img, points, ...getColor(rest[0]), rest[1], rest[2]); + else CVMat().FillPoly(M(img), points.map(p => getPoint(p)), rest[0], rest[1], rest[2], rest[3] ?? C("DEFAULT_LINE_TYPE"), rest[4] ?? 0); +} + +export function fillConvexPoly(img: Mat, points: Point[], color: Color, lineType?: number, shift?: number): void; +export function fillConvexPoly(img: Mat, points: Point[], b: number, g: number, r: number, lineType?: number, shift?: number): void; +export function fillConvexPoly(img: Mat, points: Point[], ...rest: any[]) { + if (isColor(rest[0])) fillConvexPoly(img, points, ...getColor(rest[0]), rest[1], rest[2]); + else CVMat().FillConvexPoly(M(img), points.map(p => getPoint(p)), rest[0], rest[1], rest[2], rest[3] ?? C("DEFAULT_LINE_TYPE"), rest[4] ?? 0); +} + +export function drawMarker(img: Mat, pos: Point, color: Color, markerType?: number, markerSize?: number, lineType?: number, shift?: number): void; +export function drawMarker(img: Mat, x: number, y: number, b: number, g: number, r: number, markerType?: number, markerSize?: number, lineType?: number, shift?: number): void; +export function drawMarker(img: Mat, ...rest: any[]) { + if (isPoint(rest[0])) drawMarker(img, ...getPoint(rest[0]), ...getColor(rest[1]), rest[2], rest[3], rest[4], rest[5]); + else CVMat().DrawMarker(M(img), rest[0], rest[1], rest[2], rest[3], rest[4], rest[5] ?? C("DEFAULT_MARKER"), rest[6] ?? 20, rest[7] ?? 1, rest[8] ?? C("DEFAULT_LINE_TYPE")); +} + +export function putText(img: Mat, text: string, org: Point, fontFace: number, fontScale: number, color: Color, thickness?: number, lineType?: number, bottomLeftOrigin?: boolean): void +export function putText(img: Mat, text: string, x: number, y: number, fontFace: number, fontScale: number, b: number, g: number, r: number, thickness?: number, lineType?: number, bottomLeftOrigin?: boolean): void +export function putText(img: Mat, text: string, ...rest: any[]) { + if (isPoint(rest[0])) putText(img, text, ...getPoint(rest[0]), rest[1], rest[2], ...getColor(rest[3]), rest[4], rest[5], rest[6]); + else CVMat().PutText(M(img), text, rest[0], rest[1], rest[2], rest[3], rest[4], rest[5], rest[6], rest[7] ?? C("DEFAULT_THICKNESS"), rest[8] ?? C("DEFAULT_LINE_TYPE"), rest[9] ?? false); +} + +export function getTextSize(text: string, fontFace: number, fontScale: number, thickness?: number) { + return CVUtil().GetTextSize(text, fontFace, fontScale, thickness ?? C("DEFAULT_THICKNESS")); +} + +export function getFontScaleFromHeight(fontFace: number, pixelHeight: number, thickness?: number) { + return CVUtil().GetFontScaleFromHeight(fontFace, pixelHeight, thickness ?? C("DEFAULT_THICKNESS")); +} diff --git a/src/cv/index.ts b/src/cv/index.ts new file mode 100644 index 0000000..102451b --- /dev/null +++ b/src/cv/index.ts @@ -0,0 +1,5 @@ +export { setConfig as config } from "./addon"; +export * from "./mat"; +export * from "./consts"; +export * from "./proc"; +export * from "./draw"; diff --git a/src/cv/mat.ts b/src/cv/mat.ts new file mode 100644 index 0000000..00277b2 --- /dev/null +++ b/src/cv/mat.ts @@ -0,0 +1,55 @@ +import { CVMat } from "./addon"; +import { FromCV, M, TypedArray } from "./common"; +import { IMREAD_COLOR_BGR } from "./consts"; + + +export class Mat { + #mat: any + public constructor(); + public constructor(rows: number, cols: number, type: number, data?: TypedArray); + public constructor(sizes: number[], type: number, data?: TypedArray); + public constructor(va?: any, vb?: any, vc?: any, vd?: any) { + const _CVMat = CVMat(); + if (va instanceof _CVMat) this.#mat = va; + if (typeof va === "number") this.#mat = new _CVMat([va, vb], vc, vd); + else if (typeof va === "undefined") this.#mat = new _CVMat(); + else if (va instanceof Array) this.#mat = new _CVMat(va, vb, vc); + } + + private get __mat__() { return this.#mat; } + + public get empty(): boolean { return this.#mat.IsEmpty(); } + public get cols(): number { return this.#mat.GetCols(); } + public get rows(): number { return this.#mat.GetRows(); } + public get type(): number { return this.#mat.GetType(); } + public get depth(): number { return this.#mat.GetDepth(); } + public get channels(): number { return this.#mat.GetChannels(); } + public get flags(): number { return this.#mat.GetFlags(); } + public get dims(): number { return this.#mat.GetDims(); } + public get isContinuous(): boolean { return this.#mat.GetIsContinuous(); } + public get isSubmatrix(): boolean { return this.#mat.GetIsSubmatrix(); } + public get elemSize(): number { return this.#mat.GetElemSize(); } + public get elemSize1(): number { return this.#mat.GetElemSize1(); } + public total(startDim?: number, endDim?: number): number { + if (startDim === null || startDim === undefined) return this.#mat.GetTotal(); + else return this.#mat.GetTotalWithDim(startDim, endDim ?? 2147483647); + } + public get size(): number[] { return this.#mat.GetSize(); } + + public clone() { return FromCV(this.#mat.Clone()); } + + public row(y: number) { return FromCV(this.#mat.Row(y)); } + public col(x: number) { return FromCV(this.#mat.Col(x)); } + public rowRange(start: number, end: number) { return FromCV(this.#mat.RowRange(start, end)); } + public colRange(start: number, end: number) { return FromCV(this.#mat.ColRange(start, end)); } + public diag(d: number) { return FromCV(this.#mat.Diag(d)); } +}; + +export function imread(filename: string, flag?: number) { return FromCV(CVMat().ImRead(filename, flag ?? IMREAD_COLOR_BGR)); } +export function imdecode(data: Uint8Array, flag?: number) { return FromCV(CVMat().ImDecode(data, flag ?? IMREAD_COLOR_BGR)); } +export function imwrite(filename: string, mat: Mat, params?: number[]): boolean { return CVMat().ImWrite(M(mat), filename, params); } +export function imencode(ext: string, mat: Mat, params?: number[]): Uint8Array | null { + const res = CVMat().ImEncode(M(mat), ext, params); + if (!res) return null; + return new Uint8Array(res); +} diff --git a/src/cv/proc.ts b/src/cv/proc.ts new file mode 100644 index 0000000..5265700 --- /dev/null +++ b/src/cv/proc.ts @@ -0,0 +1,53 @@ +import { Mat } from "./mat"; +import { FromCV, M, resolveArgs } from "./common"; +import { BORDER_CONSTANT, BORDER_DEFAULT, INTER_LINEAR } from "./consts"; +import { CVMat } from "./addon"; + +type CropRange = { x: number, y: number, width: number, height: number } | Array<{ start: number, end: number }>; +export function crop(src: Mat, range: CropRange): Mat; +export function crop(src: Mat, dst: Mat, range: CropRange): Mat; +export function crop(...args: any[]) { + let [src, dst, range] = resolveArgs<[src: Mat, dst: Mat, range: CropRange]>(args, args[1] instanceof Mat); + if (!(range instanceof Array)) range = [{ start: range.y, end: range.y + range.height }, { start: range.x, end: range.x + range.width }]; + return FromCV(CVMat().Crop(M(src), M(dst), range)); +} + +export function resize(src: Mat, width: number, height: number, fx?: number, fy?: number, interpolation?: number): Mat; +export function resize(src: Mat, dst: Mat, width: number, height: number, fx?: number, fy?: number, interpolation?: number): Mat; +export function resize(...args: any[]) { + const [src, dst, width, height, fx, fy, interpolation] = resolveArgs<[src: Mat, dst: Mat, width: number, height: number, fx?: number, fy?: number, interpolation?: number]>(args, args[1] instanceof Mat); + return FromCV(CVMat().Resize(M(src), M(dst), width, height, fx ?? 0, fy ?? 0, interpolation ?? INTER_LINEAR)); +} + +export function blur(src: Mat, kernelWidth: number, kernelHeight: number, anchorX?: number, anchorY?: number, borderType?: number): Mat +export function blur(src: Mat, dst: Mat, kernelWidth: number, kernelHeight: number, anchorX?: number, anchorY?: number, borderType?: number): Mat +export function blur(...args: any[]) { + let [src, dst, kernelWidth, kernelHeight, anchorX = -1, anchorY = -1, borderType = BORDER_DEFAULT] = resolveArgs<[src: Mat, dst: Mat, kernelWidth: number, kernelHeight: number, anchorX?: number, anchorY?: number, borderType?: number]>(args, args[1] instanceof Mat); + if (anchorX == -1 || anchorY == -1) anchorX = anchorY = -1; + return FromCV(CVMat().Blur(M(src), M(dst), kernelWidth, kernelHeight, anchorX, anchorY, borderType)); +} + +export function copyMakeBorder(src: Mat, top: number, bottom: number, left: number, right: number, borderType: number): Mat +export function copyMakeBorder(src: Mat, dst: Mat, top: number, bottom: number, left: number, right: number, borderType: number): Mat +export function copyMakeBorder(...args: any[]) { + const [src, dst, top, bottom, left, right, borderType] = resolveArgs<[src: Mat, dst: Mat, top: number, bottom: number, left: number, right: number, borderType: number]>(args, args[1] instanceof Mat); + return FromCV(CVMat().CopyMakeBorder(M(src), M(dst), top, bottom, left, right, borderType)); +} + +type FlipCode = 0 | 1 | -1 | "x" | "y" | "both"; +export function flip(src: Mat, flipCode: FlipCode): Mat +export function flip(src: Mat, dst: Mat, flipCode: FlipCode): Mat +export function flip(...args: any[]) { + let [src, dst, flipCode] = resolveArgs<[src: Mat, dst: Mat, flipCode: FlipCode]>(args, args[1] instanceof Mat); + if (typeof flipCode == "string") flipCode = ({ x: 0, y: 1, both: -1 } as Record<"x" | "y" | "both", 0 | 1 | -1>)[flipCode]; + return FromCV(CVMat().Flip(M(src), M(dst), flipCode)); +} + +export function warpAffine(src: Mat, transfromMat: Mat, dwidth: number, dheight: number, flags?: number, borderMode?: number): Mat; +export function warpAffine(src: Mat, dst: Mat, transfromMat: Mat, dwidth: number, dheight: number, flags?: number, borderMode?: number): Mat; +export function warpAffine(...args: any[]) { + const [src, dst, transfromMat, dwidth, dheight, flags, borderMode] = resolveArgs<[src: Mat, dst: Mat, transfromMat: Mat, dwidth: number, dheight: number, flags?: number, borderMode?: number]>(args, () => args[2] instanceof Mat); + return FromCV(CVMat().WarpAffine(M(src), M(dst), M(transfromMat), dwidth, dheight, flags ?? INTER_LINEAR, borderMode ?? BORDER_CONSTANT)); +} + +export function getRotationMatrix2D(centerX: number, centerY: number, angle: number, scale: number) { return FromCV(CVMat().GetRotationMatrix2D(centerX, centerY, angle, scale)); } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4a58679 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +import * as cv from "./cv"; +export default cv; +export { cv }; \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..814932d --- /dev/null +++ b/src/test.ts @@ -0,0 +1,34 @@ +import cv from "."; + + +async function test() { + const fs = await import("fs"); + // const buffer = fs.readFileSync("data/im1.jpeg") + const res = await cv.imread("test_data/im1.jpeg"); + // const res = await cv.imdecode(buffer); + const cropIm = cv.crop(res, { x: 10, y: 10, width: 300, height: 200 }); + + console.log(cv.imwrite("test_data/cropIm.jpg", cropIm)); + fs.writeFileSync("test_data/base.jpg", cv.imencode(".jpg", res)!); + + const rotated = cv.warpAffine(res, cv.getRotationMatrix2D(100, 100, 50, 1), res.cols, res.rows); + cv.imwrite("test_data/rotated.jpg", rotated); + + cv.imwrite("test_data/blur.jpg", cv.blur(res, 20, 20)); + + cv.imwrite("test_data/copyMakeBorder.jpg", cv.copyMakeBorder(res, 100, 30, 50, 40, cv.BORDER_CONSTANT)); + + cv.imwrite("test_data/flip.jpg", cv.flip(res, "both")); + + cv.rectangle(res, [10, 10, 200, 200], [0, 0, 255], 3); + cv.circle(res, [200, 200], 200, [255, 0, 0], 4); + cv.line(res, [100, 100], [300, 500], [0, 0, 255], 3); + cv.ellipse(res, [300, 500], [200, 100], 45, 0, 360, [0, 255, 0], 3); + cv.polylines(res, [[50, 50], [150, 150], [50, 150]], true, [255, 0, 255], 3); + cv.fillConvexPoly(res, [[50, 50], [150, 150], [150, 50]], [255, 0, 255]); + cv.drawMarker(res, [300, 200], [255, 255, 0]); + cv.imwrite("test_data/draw.jpg", res); +} + + +test(); \ No newline at end of file diff --git a/test_data/im1.jpeg b/test_data/im1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..be2ceb9f58ef4706cfae540bda5effd474e43777 GIT binary patch literal 85606 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%0Dp%k(TRDA@Ug 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 literal 0 HcmV?d00001 diff --git a/thirdpart/cmake-js-util.js b/thirdpart/cmake-js-util.js new file mode 100644 index 0000000..2189177 --- /dev/null +++ b/thirdpart/cmake-js-util.js @@ -0,0 +1,18 @@ +const os = require("os"); +const path = require("path"); +const { execSync, spawnSync, exec } = require("child_process"); + +const args = process.argv.slice(2); + +const cmakeJS = path.join(__dirname, "../node_modules/.bin/cmake-js") + + +function runCmakeJS(args) { + const child = exec(`${cmakeJS} ${args.join(" ")}`, (err, stdout, stderr) => console.log(stdout)); + child.once("close", code => process.exit(code)); +} + +if (args.includes("--include")) runCmakeJS(["print-cmakejs-include"]); +else if (args.includes("--src")) runCmakeJS(["print-cmakejs-src"]); +else if (args.includes("--lib")) runCmakeJS(["print-cmakejs-lib"]); +else if (args.includes("--napi")) console.log(require("node-addon-api").include.replace(/^"/, "").replace(/"$/, "")); \ No newline at end of file diff --git a/thirdpart/install.js b/thirdpart/install.js new file mode 100644 index 0000000..89569c3 --- /dev/null +++ b/thirdpart/install.js @@ -0,0 +1,175 @@ + + +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const args = process.argv.slice(2); +const THIRDPARTY_DIR = __dirname; + +function findArg(argname, isBoolean) { + let prefix = `--${argname}`; + let idx = args.findIndex(arg => arg == prefix); + if (idx >= 0) { + if (isBoolean) return true; + else return args[idx + 1]; + } + idx = args.findIndex(arg => arg.startsWith(prefix)) + if (idx >= 0) return args[idx].substring(prefix.length + 1); + return null; +} + +function assert(cond, message) { + if (!cond) { + console.error(message); + process.exit(1); + } +} + +const buildOptions = { + withOpenCV: findArg("with-opencv", true) ?? false, + buildType: findArg("build-type", false) ?? "Release", + proxy: findArg("proxy"), + generator: findArg("generator"), +} + +const spawnOption = { + stdio: "inherit", env: { + ...process.env, ...buildOptions.proxy ? { + "http_proxy": buildOptions.proxy, + "https_proxy": buildOptions.proxy, + } : {} + } +}; + +function P(path) { return path.replace(/\\/g, "/"); } + +function checkFile(...items) { + return fs.existsSync(path.resolve(...items)); +} + +function downloadSource(name, repo, branch, recursive, checkedFile) { + const sourceRootDir = path.join(THIRDPARTY_DIR, "_source"); + fs.mkdirSync(sourceRootDir, { recursive: true }); + const sourceDir = path.join(sourceRootDir, name); + if (checkFile(sourceDir, checkedFile)) return sourceDir; + + console.log(`${name}源代码不存在,正在下载...`); + assert(spawnSync("git", [ + "clone", + ...recursive ? ["--recursive"] : [], + repo, "-b", branch, + sourceDir + ], { ...spawnOption }).status == 0, `下载${name}源代码失败`); + + return sourceDir; +} + +function cmakeBuildFromSource(name, repo, branch, downloader, buildArgs, cmakeCreator) { + const sourceDir = downloader ? downloader(name, repo, branch) : downloadSource(name, repo, branch, false, "CMakeLists.txt"); + const buildDir = path.join(sourceDir, "build", buildOptions.buildType); + const installDir = path.join(THIRDPARTY_DIR, name, buildOptions.buildType); + const configFile = path.join(installDir, "config.cmake"); + + if (typeof buildArgs == "function") buildArgs = buildArgs(sourceDir, buildDir, installDir); + + //编译源代码 + if (checkFile(configFile)) return + console.log(`${name}目标不存在,正在编译...`); + fs.rmSync(buildDir, { recursive: true, force: true }); + fs.mkdirSync(buildDir, { recursive: true }); + + assert(spawnSync("cmake", [ + "-G", buildOptions.generator ?? "Ninja", sourceDir, + "-DCMAKE_BUILD_TYPE=" + buildOptions.buildType, + "-DCMAKE_INSTALL_PREFIX=" + installDir, + ...(buildArgs ?? []).map(arg => arg.startsWith("-D") ? arg : `-D${arg}`), + ], { ...spawnOption, cwd: buildDir }).status == 0, `配置${name}失败`); + + assert(spawnSync("cmake", [ + "--build", ".", + "--parallel", os.cpus().length.toString(), + "--config", buildOptions.buildType, + "--target", "install", + ], { ...spawnOption, cwd: buildDir }).status == 0, `编译${name}失败`); + + fs.writeFileSync(configFile, cmakeCreator(installDir)); +} + +async function downloadFromURL(name, url, resolver) { + const cacheDir = path.join(THIRDPARTY_DIR, "_cache"); + fs.mkdirSync(cacheDir, { recursive: true }); + const saveName = path.join(cacheDir, path.basename(url)); + const outputDir = path.join(cacheDir, name); + fs.rmSync(outputDir, { force: true, recursive: true }); + + if (!checkFile(saveName)) { + console.log(`开始下载${name}, 地址:${url}`); + await fetch(url).then(res => { + console.log(res.status) + }) + const result = spawnSync("curl", ["-o", saveName + ".cache", "-L", url, "-s", "-w", "%{http_code}"], { ...spawnOption, stdio: "pipe" }); + assert(result.status == 0 && result.stdout.toString() == "200", `下载${name}失败`); + fs.renameSync(saveName + ".cache", saveName); + } + let currentName = saveName; + + if (saveName.endsWith(".bz2")) { + currentName = saveName.substring(0, saveName.length - 4); + const input = fs.createReadStream(saveName); + const out = fs.createWriteStream(currentName); + const bz2 = require("unbzip2-stream")(); + await new Promise((resolve, reject) => { + input.pipe(bz2).pipe(out); + out.on("close", resolve); + input.on("error", reject); + bz2.on("error", reject); + out.on("error", reject); + }); + } + + const format = path.extname(currentName); + assert([".tar", ".gz", ".tgz", ".zip"].includes(format), "仅支持.tar, .gz, .tgz, .zip格式的压缩文件"); + const method = { + ".tar": require("compressing").tar, + ".gz": require("compressing").gzip, + ".tgz": require("compressing").tgz, + ".zip": require("compressing").zip, + }[format]; + await method.uncompress(currentName, outputDir); + + await resolver(outputDir); + + fs.rmSync(outputDir, { recursive: true, force: true }); +} + + +async function main() { + //OpenCV + if (buildOptions.withOpenCV) cmakeBuildFromSource("OpenCV", "https://github.com/opencv/opencv.git", "4.11.0", null, [ + "-DBUILD_SHARED_LIBS=OFF", + "-DBUILD_opencv_apps=OFF", + "-DBUILD_opencv_js=OFF", + "-DBUILD_opencv_python2=OFF", + "-DBUILD_opencv_python3=OFF", + "-DBUILD_ANDROID_PROJECTS=OFF", + "-DBUILD_ANDROID_EXAMPLES=OFF", + "-DBUILD_TESTS=OFF", + "-DBUILD_FAT_JAVA_LIB=OFF", + "-DBUILD_ANDROID_SERVICE=OFF", + "-DBUILD_JAVA=OFF", + "-DBUILD_PERF_TESTS=OFF" + ], (root) => [ + `set(OpenCV_STATIC ON)`, + os.platform() == "win32" ? + `include(${JSON.stringify(P(path.join(root, "OpenCVConfig.cmake")))})` : + `include(${JSON.stringify(P(path.join(root, "lib/cmake/opencv4/OpenCVConfig.cmake")))})`, + `set(OpenCV_INCLUDE_DIR \${OpenCV_INCLUDE_DIRS})`, + os.platform() == "win32" ? + "set(OpenCV_LIB_DIR ${OpenCV_LIB_PATH})" : + `set(OpenCV_LIB_DIR ${JSON.stringify(P(path.join(root, "lib")))})`, + // `set(OpenCV_LIBS OpenCV_LIBS)`, + ].join("\n")); +} + +main(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..838b119 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,108 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + "declarationDir": "./typing", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}