Building Node.js C++ Addon for Document Edge Detection and Rectification
Tags: node, javascript, cpp, programming
Published: 2023-11-08 01:52:18

A Node.js C++ addon is a module written in C++ that can be loaded into Node.js, used to extend Node.js capabilities by allowing developers to implement functionality in C++. This article will guide you through building a Node.js C++ addon for document edge detection and rectification. The addon relies on the [Dynamsoft Document Normalizer C++ SDK](https://www.dynamsoft.com/document-normalizer/docs/introduction/?ver=latest), which offers APIs for detecting document edges and normalizing the document using the identified quadrilaterals.

## NPM Package
[https://www.npmjs.com/package/docrectifier4nodejs](https://www.npmjs.com/package/docrectifier4nodejs)

## Target Platforms
- **Windows**
- **Linux**

## Prerequisites
- [Node.js](https://nodejs.org/en/download/)
- C/C++ compiler for **Windows** or **Linux**
- node-gyp

    ```
    npm i node-gyp -g
    ```
    
    
    **node-gyp** is a cross-platform, Node.js-based command-line tool designed for compiling native addon modules specifically for Node.js. 

- [Dynamsoft Document Normalizer C++ SDK for Windows and Linux](https://www.dynamsoft.com/document-normalizer/downloads/?ver=latest)
- A 30-day FREE trial license for Dynamsoft Document Normalizer C++ SDK. You can get it from [here](https://www.dynamsoft.com/customer/license/trialLicense/?product=ddn).

## Node.js C++ Addon Project Structure

The basic structure of a Node.js C++ addon project is as follows:

```
my-addon/
├── binding.gyp
├── package.json
├── src/
│   ├── my_addon.cc
│   └── my_addon.h
└── index.js
```

- **binding.gyp** - The build configuration file for the addon.
- **package.json** - The package configuration file for the addon.
- **src/** - The C++ source code directory for the addon.
- **index.js** - The entry point of the addon.


## Implement C++ Methods for Node.js Addon
Let's get started with the header file:

```cpp
#ifndef DocRectifierSCANNER_H
#define DocRectifierSCANNER_H

#include <node.h>
#include <node_object_wrap.h>

#include "DynamsoftCore.h"
#include "DynamsoftDocumentNormalizer.h"

class DocRectifier : public node::ObjectWrap
{
public:
  static void Init(v8::Local<v8::Object> exports);
  void *handler;
  NormalizedImageResult *imageResult;

  void FreeImage();

private:
  explicit DocRectifier();
  ~DocRectifier();

  static void New(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void GetParameters(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void SetParameters(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void Save(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void DetectFileAsync(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void DetectBufferAsync(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void NormalizeBufferAsync(const v8::FunctionCallbackInfo<v8::Value> &args);
  static void NormalizeFileAsync(const v8::FunctionCallbackInfo<v8::Value> &args);
};

#endif
```

- The `DynamsoftCore.h` and `DynamsoftDocumentNormalizer.h` are the header files of Dynamsoft Document Normalizer C++ SDK.
- The `DocRectifier` class is derived from `node::ObjectWrap`. It is used to wrap the native C++ object and expose it to JavaScript.
- The `void *handler` is the handler of the `DynamsoftDocumentNormalizer` object.
- The `NormalizedImageResult *imageResult` is the pointer of the rectified document image.

Following that, we'll move to the cpp file to implement the code.

The native entry point of the addon is the `Init` method, which is responsible for exporting native methods to JavaScript.

```cpp
void Init(Local<Object> exports)
{
	NODE_SET_METHOD(exports, "initLicense", InitLicense);
	NODE_SET_METHOD(exports, "getVersionNumber", GetVersionNumber);
	DocRectifier::Init(exports);
}

NODE_MODULE(DocRectifier, Init)
```

The `initLicense` and `getVersionNumber` are global methods and not encapsulated within the `DocRectifier` class. 

```cpp
void InitLicense(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();
	Local<Context> context = isolate->GetCurrentContext();

	String::Utf8Value license(isolate, args[0]);
	char *pszLicense = *license;
	char errorMsgBuffer[512];
	int ret = DC_InitLicense(pszLicense, errorMsgBuffer, 512);
	args.GetReturnValue().Set(Number::New(isolate, ret));
}

void GetVersionNumber(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = Isolate::GetCurrent();
	args.GetReturnValue().Set(String::NewFromUtf8(
								  isolate, DDN_GetVersion())
								  .ToLocalChecked());
}
```

The `DocRectifier::Init(exports)` method exports the `DocRectifier` class and its associated methods to JavaScript. 

```cpp
void DocRectifier::Init(Local<Object> exports)
{
	Isolate *isolate = exports->GetIsolate();
	Local<Context> context = isolate->GetCurrentContext();

	Local<ObjectTemplate> addon_data_tpl = ObjectTemplate::New(isolate);
	addon_data_tpl->SetInternalFieldCount(1); 
	Local<Object> addon_data =
		addon_data_tpl->NewInstance(context).ToLocalChecked();

	Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, addon_data);
	tpl->SetClassName(String::NewFromUtf8(isolate, "DocRectifier").ToLocalChecked());
	tpl->InstanceTemplate()->SetInternalFieldCount(1);

	NODE_SET_PROTOTYPE_METHOD(tpl, "getParameters", GetParameters);
	NODE_SET_PROTOTYPE_METHOD(tpl, "setParameters", SetParameters);
	NODE_SET_PROTOTYPE_METHOD(tpl, "save", Save);
	NODE_SET_PROTOTYPE_METHOD(tpl, "detectFileAsync", DetectFileAsync);
	NODE_SET_PROTOTYPE_METHOD(tpl, "detectBufferAsync", DetectBufferAsync);
	NODE_SET_PROTOTYPE_METHOD(tpl, "normalizeBufferAsync", NormalizeBufferAsync);
	NODE_SET_PROTOTYPE_METHOD(tpl, "normalizeFileAsync", NormalizeFileAsync);

	Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked();
	addon_data->SetInternalField(0, constructor);
	exports->Set(context, String::NewFromUtf8(isolate, "DocRectifier").ToLocalChecked(),
				 constructor)
		.FromJust();
}

void DocRectifier::New(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();
	Local<Context> context = isolate->GetCurrentContext();

	if (args.IsConstructCall())
	{
		DocRectifier *obj = new DocRectifier();
		obj->Wrap(args.This());
		args.GetReturnValue().Set(args.This());
	}
	else
	{
		const int argc = 1;
		Local<Value> argv[argc] = {args[0]};
		Local<Function> cons =
			args.Data().As<Object>()->GetInternalField(0).As<Function>();
		Local<Object> result =
			cons->NewInstance(context, argc, argv).ToLocalChecked();
		args.GetReturnValue().Set(result);
	}
}

DocRectifier::DocRectifier()
{
	handler = DDN_CreateInstance();
	imageResult = NULL;
}

DocRectifier::~DocRectifier()
{
	DDN_DestroyInstance(handler);

	FreeImage();
}

void DocRectifier::FreeImage()
{
	if (imageResult != NULL)
	{
		DDN_FreeNormalizedImageResult(&imageResult);
		imageResult = NULL;
	}
}
```

The `DocRectifier` class comprises three synchronous methods and four asynchronous methods. To facilitate non-blocking operations, the asynchronous methods employ libuv's thread pool through the invocation of `uv_queue_work`. Additionally, two worker structs are defined for data transfer between the main thread and the worker thread.

```cpp
struct DetectWorker
{
	void *handler;
	uv_work_t request;				   
	Persistent<Function> callback;	  
	char filename[128];				   
	DetectedQuadResultArray *pResults; 
	unsigned char *buffer;
	int size;			  
	int errorCode;		   
	int width;			  
	int height;			   
	BufferType bufferType; 
	int stride;			   
};

struct NormalizeWorker
{
	DocRectifier *obj;
	uv_work_t request;			  
	Persistent<Function> callback; 
	char filename[128];			   
	unsigned char *buffer;
	int size;			  
	int errorCode;		   
	int width;			   
	int height;			  
	BufferType bufferType; 
	int stride;			   
	int x1, y1, x2, y2, x3, y3, x4, y4;
};
```


The `Save()` method, a synchronous function, is responsible for saving the rectified document image to a file.

```cpp
void DocRectifier::Save(const FunctionCallbackInfo<Value> &args)
{

	Isolate *isolate = args.GetIsolate();
	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());
	String::Utf8Value fileName(isolate, args[0]);

	int ret = DMERR_UNKNOWN;
	if (obj->imageResult)
	{
		ret = NormalizedImageResult_SaveToFile(obj->imageResult, *fileName);
		if (ret != DM_OK)
			printf("NormalizedImageResult_SaveToFile: %s\r\n", DC_GetErrorString(ret));
	}

	args.GetReturnValue().Set(Number::New(isolate, ret));
}
```

The `GetParameters()` method, a synchronous function, is responsible for getting the current parameters of the `DynamsoftDocumentNormalizer` object.

```cpp
void DocRectifier::GetParameters(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();

	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());

	char *content = NULL;
	DDN_OutputRuntimeSettingsToString(obj->handler, "", &content);

	args.GetReturnValue().Set(String::NewFromUtf8(
								  isolate, content)
								  .ToLocalChecked());

	if (content != NULL)
		DDN_FreeString(&content);
}
```

The `SetParameters()`, a synchronous function, is responsible for setting the parameters of the `DynamsoftDocumentNormalizer` object.

```cpp
void DocRectifier::SetParameters(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();
	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());

	String::Utf8Value params(isolate, args[0]);
	char errorMsgBuffer[512];
	int ret = DDN_InitRuntimeSettingsFromString(obj->handler, *params, errorMsgBuffer, 512);
	if (ret != DM_OK)
	{
		printf("%s\n", errorMsgBuffer);
	}

	args.GetReturnValue().Set(Number::New(isolate, ret));
}
```

The `DetectFileAsync()` and `DetectBufferAsync()` methods are asynchronous. They detect the edges of a document image and return the coordinates of the found quadrilaterals.

```cpp
void DocRectifier::DetectFileAsync(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();
	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());
	Local<Context> context = isolate->GetCurrentContext();

	String::Utf8Value fileName(isolate, args[0]); 
	char *pFileName = *fileName;
	Local<Function> cb = Local<Function>::Cast(args[1]); 

	DetectWorker *worker = new DetectWorker;
	worker->handler = obj->handler;
	worker->request.data = worker;
	strcpy(worker->filename, pFileName);
	worker->callback.Reset(isolate, cb);
	worker->pResults = NULL;
	worker->buffer = NULL;
	worker->bufferType = NO_BUFFER;

	uv_queue_work(uv_default_loop(), &worker->request, (uv_work_cb)DetectionWorking, (uv_after_work_cb)DetectionDone);
}
```

The `DetectionWorking()` method is the worker thread function, which is responsible for detecting the edges of a document image. 

```cpp
static void DetectionWorking(uv_work_t *req)
{
	DetectWorker *worker = static_cast<DetectWorker *>(req->data);
	if (!worker->handler)
	{
		printf("DocRectifier handler not initialized.\n");
		return;
	}

	DetectedQuadResultArray *pResults = NULL;

	int ret = 0;
	switch (worker->bufferType)
	{
	case RGB_BUFFER:
	{
		if (worker->buffer)
		{
			int width = worker->width, height = worker->height, stride = worker->stride;
			ImagePixelFormat format = IPF_RGB_888;

			if (width == stride)
			{
				format = IPF_GRAYSCALED;
			}
			else if (((width * 3 + 3) & ~3) == stride)
			{
				format = IPF_RGB_888;
			}
			else if (width * 4 == stride)
			{
				format = IPF_ARGB_8888;
			}

			ImageData data;
			data.bytes = worker->buffer;
			data.width = width;
			data.height = height;
			data.stride = stride;
			data.format = format;
			data.bytesLength = stride * height;
			data.orientation = 0;

			ret = DDN_DetectQuadFromBuffer(worker->handler, &data, "", &pResults);
		}
		break;
	}
	default:
	{
		ret = DDN_DetectQuadFromFile(worker->handler, worker->filename, "", &pResults);
	}
	}

	if (ret)
	{
		printf("Detection error: %s\n", DC_GetErrorString(ret));
	}

	worker->errorCode = ret;
	worker->pResults = pResults;
}
```

The `DetectionDone()` method serves as the callback function for the worker thread. It is triggered upon the completion of the worker thread's task, returning the coordinates of the detected quadrilaterals.

```cpp
static void DetectionDone(uv_work_t *req, int status)
{
	Isolate *isolate = Isolate::GetCurrent();
	HandleScope scope(isolate);
	Local<Context> context = isolate->GetCurrentContext();

	DetectWorker *worker = static_cast<DetectWorker *>(req->data);

	DetectedQuadResultArray *pResults = worker->pResults;
	int errorCode = worker->errorCode;

	Local<Array> documentResults = Array::New(isolate);

	if (pResults)
	{
		int count = pResults->resultsCount;
		for (int i = 0; i < count; i++)
		{
			DetectedQuadResult *quadResult = pResults->detectedQuadResults[i];
			int confidence = quadResult->confidenceAsDocumentBoundary;
			DM_Point *points = quadResult->location->points;
			int x1 = points[0].coordinate[0];
			int y1 = points[0].coordinate[1];
			int x2 = points[1].coordinate[0];
			int y2 = points[1].coordinate[1];
			int x3 = points[2].coordinate[0];
			int y3 = points[2].coordinate[1];
			int x4 = points[3].coordinate[0];
			int y4 = points[3].coordinate[1];

			Local<Object> result = Object::New(isolate);
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "confidence", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, confidence));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "x1", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, x1));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "y1", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, y1));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "x2", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, x2));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "y2", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, y2));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "x3", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, x3));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "y3", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, y3));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "x4", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, x4));
			result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "y4", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, y4));
			documentResults->Set(context, Number::New(isolate, i), result);
		}

		DDN_FreeDetectedQuadResultArray(&pResults);
	}

	const unsigned argc = 2;
	Local<Number> err = Number::New(isolate, errorCode);
	Local<Value> argv[argc] = {err, documentResults};
	Local<Function> cb = Local<Function>::New(isolate, worker->callback);
	cb->Call(context, Null(isolate), argc, argv);

	delete worker;
}
```

The `NormalizeFileAsync()` and `NormalizeBufferAsync()` methods are asynchronous. They rectify the document image based on the found quadrilaterals.

```cpp
void DocRectifier::NormalizeBufferAsync(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = Isolate::GetCurrent();
	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());
	Local<Context> context = isolate->GetCurrentContext();

	unsigned char *buffer = (unsigned char *)node::Buffer::Data(args[0]);
	int width = args[1]->Int32Value(context).ToChecked();
	int height = args[2]->Int32Value(context).ToChecked();
	int stride = args[3]->Int32Value(context).ToChecked();
	int x1 = args[4]->Int32Value(context).ToChecked();
	int y1 = args[5]->Int32Value(context).ToChecked();
	int x2 = args[6]->Int32Value(context).ToChecked();
	int y2 = args[7]->Int32Value(context).ToChecked();
	int x3 = args[8]->Int32Value(context).ToChecked();
	int y3 = args[9]->Int32Value(context).ToChecked();
	int x4 = args[10]->Int32Value(context).ToChecked();
	int y4 = args[11]->Int32Value(context).ToChecked();
	Local<Function> cb = Local<Function>::Cast(args[12]);

	NormalizeWorker *worker = new NormalizeWorker;
	worker->request.data = worker;
	worker->callback.Reset(isolate, cb);
	worker->buffer = buffer;
	worker->width = width;
	worker->height = height;
	worker->bufferType = RGB_BUFFER;
	worker->stride = stride;
	worker->obj = obj;
	worker->x1 = x1;
	worker->y1 = y1;
	worker->x2 = x2;
	worker->y2 = y2;
	worker->x3 = x3;
	worker->y3 = y3;
	worker->x4 = x4;
	worker->y4 = y4;

	uv_queue_work(uv_default_loop(), &worker->request, (uv_work_cb)NormalizeWorking, (uv_after_work_cb)NormalizeDone);
}

void DocRectifier::NormalizeFileAsync(const FunctionCallbackInfo<Value> &args)
{
	Isolate *isolate = args.GetIsolate();
	DocRectifier *obj = ObjectWrap::Unwrap<DocRectifier>(args.Holder());
	Local<Context> context = isolate->GetCurrentContext();

	String::Utf8Value fileName(isolate, args[0]); 
	char *pFileName = *fileName;
	int x1 = args[1]->Int32Value(context).ToChecked();
	int y1 = args[2]->Int32Value(context).ToChecked();
	int x2 = args[3]->Int32Value(context).ToChecked();
	int y2 = args[4]->Int32Value(context).ToChecked();
	int x3 = args[5]->Int32Value(context).ToChecked();
	int y3 = args[6]->Int32Value(context).ToChecked();
	int x4 = args[7]->Int32Value(context).ToChecked();
	int y4 = args[8]->Int32Value(context).ToChecked();
	Local<Function> cb = Local<Function>::Cast(args[9]);
	NormalizeWorker *worker = new NormalizeWorker;
	worker->request.data = worker;
	strcpy(worker->filename, pFileName);
	worker->callback.Reset(isolate, cb);
	worker->buffer = NULL;
	worker->bufferType = NO_BUFFER;
	worker->obj = obj;
	worker->x1 = x1;
	worker->y1 = y1;
	worker->x2 = x2;
	worker->y2 = y2;
	worker->x3 = x3;
	worker->y3 = y3;
	worker->x4 = x4;
	worker->y4 = y4;

	uv_queue_work(uv_default_loop(), &worker->request, (uv_work_cb)NormalizeWorking, (uv_after_work_cb)NormalizeDone);
}
```

The `NormalizeWorking()` method is the worker thread function, which is responsible for rectifying the document image based on the found quadrilaterals.

```cpp
static void NormalizeWorking(uv_work_t *req)
{
	NormalizeWorker *worker = static_cast<NormalizeWorker *>(req->data);
	if (!worker->obj->handler)
	{
		printf("DocRectifier handler not initialized.\n");
		return;
	}

	worker->obj->FreeImage();

	Quadrilateral quad;
	quad.points[0].coordinate[0] = worker->x1;
	quad.points[0].coordinate[1] = worker->y1;
	quad.points[1].coordinate[0] = worker->x2;
	quad.points[1].coordinate[1] = worker->y2;
	quad.points[2].coordinate[0] = worker->x3;
	quad.points[2].coordinate[1] = worker->y3;
	quad.points[3].coordinate[0] = worker->x4;
	quad.points[3].coordinate[1] = worker->y4;

	int ret = 0;
	switch (worker->bufferType)
	{
	case RGB_BUFFER:
	{
		if (worker->buffer)
		{
			int width = worker->width, height = worker->height, stride = worker->stride;
			ImagePixelFormat format = IPF_RGB_888;

			if (width == stride)
			{
				format = IPF_GRAYSCALED;
			}
			else if (((width * 3 + 3) & ~3) == stride)
			{
				format = IPF_RGB_888;
			}
			else if (width * 4 == stride)
			{
				format = IPF_ARGB_8888;
			}

			ImageData data;
			data.bytes = worker->buffer;
			data.width = width;
			data.height = height;
			data.stride = stride;
			data.format = format;
			data.bytesLength = stride * height;
			data.orientation = 0;

			ret = DDN_NormalizeBuffer(worker->obj->handler, &data, "", &quad, &worker->obj->imageResult);
		}
		break;
	}
	default:
	{
		ret = DDN_NormalizeFile(worker->obj->handler, worker->filename, "", &quad, &worker->obj->imageResult);
	}
	}

	if (ret)
	{
		printf("Detection error: %s\n", DC_GetErrorString(ret));
	}

	worker->errorCode = ret;
}
```

The `NormalizeDone()` serves as the callback function for the worker thread. It is triggered upon the completion of the worker thread's task, returning the rectified document image.

```cpp
static void NormalizeDone(uv_work_t *req, int status)
{
	Isolate *isolate = Isolate::GetCurrent();
	HandleScope scope(isolate);
	Local<Context> context = isolate->GetCurrentContext();

	NormalizeWorker *worker = static_cast<NormalizeWorker *>(req->data);

	int errorCode = worker->errorCode;
	Local<Object> result = Object::New(isolate);

	if (worker->obj->imageResult)
	{
		ImageData *imageData = worker->obj->imageResult->image;
		int width = imageData->width;
		int height = imageData->height;
		int stride = imageData->stride;
		int format = (int)imageData->format;
		unsigned char *data = imageData->bytes;
		int orientation = imageData->orientation;
		int length = imageData->bytesLength;

		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "width", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, width));
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "height", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, height));
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "stride", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, stride));
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "format", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, format));
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "orientation", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, orientation));
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "length", NewStringType::kNormal).ToLocalChecked(), Number::New(isolate, length));

		unsigned char *rgba = (unsigned char *)calloc(width * height * 4, sizeof(unsigned char));

		if (format == IPF_RGB_888)
		{
			int dataIndex = 0;
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					int index = i * width + j;

					rgba[index * 4] = data[dataIndex];		   
					rgba[index * 4 + 1] = data[dataIndex + 1]; 
					rgba[index * 4 + 2] = data[dataIndex + 2]; 
					rgba[index * 4 + 3] = 255;				  
					dataIndex += 3;
				}
			}
		}
		else if (format == IPF_GRAYSCALED)
		{
			int dataIndex = 0;
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					int index = i * width + j;
					rgba[index * 4] = data[dataIndex];
					rgba[index * 4 + 1] = data[dataIndex];
					rgba[index * 4 + 2] = data[dataIndex];
					rgba[index * 4 + 3] = 255;
					dataIndex += 1;
				}
			}
		}
		else if (format == IPF_BINARY)
		{
			unsigned char *grayscale = (unsigned char *)calloc(width * height, sizeof(unsigned char));
			binary2grayscale(data, grayscale, width, height, stride, length);

			int dataIndex = 0;
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					int index = i * width + j;
					rgba[index * 4] = grayscale[dataIndex];
					rgba[index * 4 + 1] = grayscale[dataIndex];
					rgba[index * 4 + 2] = grayscale[dataIndex];
					rgba[index * 4 + 3] = 255;
					dataIndex += 1;
				}
			}

			free(grayscale);
		}

		std::vector<uint8_t> rawBytes(rgba, rgba + width * height * 4);
		result->DefineOwnProperty(context, String::NewFromUtf8(isolate, "data", NewStringType::kNormal).ToLocalChecked(), node::Buffer::Copy(isolate, (char *)rawBytes.data(), rawBytes.size()).ToLocalChecked());
		free(rgba);
	}

	const unsigned argc = 2;
	Local<Number> err = Number::New(isolate, errorCode);
	Local<Value> argv[argc] = {err, result};
	Local<Function> cb = Local<Function>::New(isolate, worker->callback);
	cb->Call(context, Null(isolate), argc, argv);

	delete worker;
}
```

## Configure Binding.gyp for Compiling Node.js C++ Addon
In the `binding.gyp` file, we define the specifications for library linking and file copying across various platforms.

```json
'conditions': [
    ['OS=="linux"', {
        'defines': [
            'LINUX_DOCRECTIFIER',
        ],

        "cflags": [
            "-std=c++11"
        ],
        'ldflags': [
            "-Wl,-rpath,'$$ORIGIN'"
        ],

        'libraries': [
            "-lDynamsoftCore", "-lDynamsoftDocumentNormalizer", "-L../lib/linux"
        ],
        'copies': [
            {
                'destination': 'build/Release/',
                'files': [
                    './lib/linux/libDynamicImage.so',
                    './lib/linux/libDynamicPdf.so',
                    './lib/linux/libDynamicPdfCore.so',
                    './lib/linux/libDynamsoftCore.so',
                    './lib/linux/libDynamsoftDocumentNormalizer.so',
                    './lib/linux/libDynamsoftImageProcessing.so',
                    './lib/linux/libDynamsoftIntermediateResult.so',
                ]
            }
        ]
    }],
    ['OS=="win"', {
        'defines': [
            'WINDOWS_DOCRECTIFIER',
        ],
        'libraries': [
            "-l../lib/windows/DynamsoftCorex64.lib", "-l../lib/windows/DynamsoftDocumentNormalizerx64.lib"
        ],
        'copies': [
            {
                'destination': 'build/Release/',
                'files': [
                    './lib/windows/DynamicImagex64.dll',
                    './lib/windows/DynamicPdfCorex64.dll',
                    './lib/windows/DynamicPdfx64.dll',
                    './lib/windows/DynamsoftCorex64.dll',
                    './lib/windows/DynamsoftDocumentNormalizerx64.dll',
                    './lib/windows/DynamsoftImageProcessingx64.dll',
                    './lib/windows/DynamsoftIntermediateResultx64.dll',
                    './lib/windows/vcomp140.dll',
                ]
            }
        ],
    }]
]
```


## Export the Native Methods to JavaScript

The `index.js` file acts as the entry point for the Node.js addon, responsible for exporting the native methods to JavaScript.

```javascript
var docrectifier = require('./build/Release/docrectifier');
var DocRectifier = /** @class */ (function () {
    function DocRectifier() {
        this.obj = docrectifier.DocRectifier();
    }
    DocRectifier.initLicense = function (license) {
        return docrectifier.initLicense(license);
    };
    DocRectifier.getVersionNumber = function () {
        return docrectifier.getVersionNumber();
    };
    DocRectifier.prototype.save = function (filePath) {
        var _this = this;
        return _this.obj.save(filePath);
    };
    DocRectifier.prototype.getParameters = function () {
        var _this = this;
        return _this.obj.getParameters();
    };
    DocRectifier.prototype.setParameters = function (params) {
        var _this = this;
        return _this.obj.setParameters(params);
    };
    DocRectifier.prototype.detectFileAsync = function () {
        var _this = this;
        var filename = arguments[0];
        var callback = arguments[1];
        const promise = new Promise((resolve, reject) => {
            _this.obj.detectFileAsync(filename, function (err, result) {
                setTimeout(function () {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(result);
                    }
                }, 0);
            });
        });

        if (callback && typeof callback === 'function') {
            promise
                .then(result => callback(null, result))
                .catch(err => callback(err));
        } else {
            return promise;
        }
    };
    DocRectifier.prototype.detectBufferAsync = function () {
        ...
    };
    DocRectifier.prototype.normalizeFileAsync = function () {
        ...
    };
    DocRectifier.prototype.normalizeBufferAsync = function () {
        ...
    };
    DocRectifier.Template = Template;
    return DocRectifier;
}());
module.exports = DocRectifier;
```

The asynchronous methods are designed to support both callback functions and promises.


## Node.js Examples for Document Rectification
In the subsequent sections, we will develop two examples to illustrate the usage of the Node.js C++ addon for document edge detection and rectification.

```bash
npm install docrectifier4nodejs
```

### Command Line
Set the `LICENSE-KEY` and specify the `image-file` in the code below:

```javascript
const DocRectifier = require('docrectifier4nodejs');
console.log(DocRectifier.getVersionNumber());
DocRectifier.initLicense('LICENSE-KEY');

var obj = new DocRectifier();
(async function () {
    try {
        obj.setParameters(DocRectifier.Template.color);
        let results = await obj.detectFileAsync('image-file');
        console.log(results);
        let result = results[0];
        result = await obj.normalizeFileAsync('image-file', result['x1'], result['y1'], result['x2'], result['y2'], result['x3'], result['y3'], result['x4'], result['y4']);
        obj.save('test.png');
    } catch (error) {
        console.log(error);
    }
})();

```

![Node.js command line document rectification](https://www.dynamsoft.com/codepool/img/2023/11/nodejs-command-line-document-rectification.png)

### Web
1. Install `express`, `multer` and `sharp`:

    ```bash
    npm install express multer sharp
    ```

2. Create an `app.js` file and add the following code to initiate a web server:
      
    ```javascript
    const express = require('express');
    const multer = require('multer');
    const sharp = require('sharp');
    const app = express();
    const http = require('http');
    const server = http.createServer(app);
    const bodyParser = require('body-parser');
    const upload = multer({ dest: 'uploads/' });
    const DocRectifier = require('docrectifier4nodejs');
    DocRectifier.initLicense('LICENSE-KEY');
    var obj = new DocRectifier();
    obj.setParameters(DocRectifier.Template.color);
    
    app.use(express.static('public'));
    app.use('/node_modules', express.static(__dirname + '/node_modules'));
    const port = process.env.PORT || 3000;
    
    server.listen(port, '0.0.0.0', () => {
        host = server.address().address;
        console.log(`Server running at http://0.0.0.0:${port}/`);
    });
    ```


3. Include an HTTP POST endpoint in the application for uploading an image file. This endpoint will handle the rectification of the document image and return the rectified image to the client:

    ```javascript
    app.post('/rectifydocument', upload.single('image'), async (req, res) => {
        const file = req.file;
    
        if (!file) {
            return res.status(400).send('No file uploaded.');
        }
    
        try {
            console.log('Uploaded file details:', file);
            let results = await obj.detectFileAsync(file.path);
            console.log(results);
            let result = results[0];
            result = await obj.normalizeFileAsync(file.path, result['x1'], result['y1'], result['x2'], result['y2'], result['x3'], result['y3'], result['x4'], result['y4']);
            console.log(result);
    
            let data = result['data']
            let width = result['width']
            let height = result['height']
            for (let i = 0; i < data.length; i += 4) {
                const red = data[i];
                const blue = data[i + 2];
                data[i] = blue;
                data[i + 2] = red;
            }
            sharp(data, {
                raw: {
                    width: width,
                    height: height,
                    channels: 4
                }
            })
                .jpeg() 
                .toBuffer() 
                .then(jpegBuffer => {
                    res.set('Content-Type', 'application/octet-stream');
                    res.send(jpegBuffer);
                })
                .catch(err => {
                    console.error(err);
                    res.status(500).send('An error occurred while processing the image.');
                });
        }
        catch (err) {
            console.error(err);
            return res.status(500).send('An error occurred while processing the image.');
        }
    
    });
    
    ```

4. Design a web page and incorporate a button click event that uploads an image file selected through the `<input type="file">` element. This will also display the rectified image returned from the server.

    ```javascript
    async function rectifyDocument() {
        const input = document.getElementById('document-file');
        const file = input.files[0];
    
        if (!file) {
            return;
        }
    
        const formData = new FormData();
        formData.append('image', file);
    
        let response = await fetch('/rectifydocument', {
            method: 'POST',
            body: formData
        });
    
        if (response.headers.get('Content-Type').includes('text/plain')) {
            let data = await response.text();
            document.getElementById('document-result').innerHTML = data;
    
            let divElement = document.getElementById("document-result");
            divElement.style.display = "block";
    
            divElement = document.getElementById("document-rectified-image");
            divElement.style.display = "none";
        }
        else if (response.headers.get('Content-Type').includes('application/octet-stream')) {
            let data = await response.blob();
            let img = document.getElementById('document-rectified-image');
            let url = URL.createObjectURL(data);
            img.src = url;
    
            let divElement = document.getElementById("document-rectified-image");
            divElement.style.display = "block";
    
            divElement = document.getElementById("document-result");
            divElement.style.display = "none";
        }
    }
    ```

    <img src="https://www.dynamsoft.com/codepool/img/2023/11/nodejs-web-document-rectification.png" width="600px" alt="Node.js web document rectification">

## Source Code
[https://github.com/yushulx/nodejs-document-rectification](https://github.com/yushulx/nodejs-document-rectification)