#include "compress.h" struct CompressProps { unsigned char *srcData; uint32_t format; uint32_t width; uint32_t stride; uint32_t height; uint32_t subsampling; int quality; int bpp; int flags; unsigned long resSize; unsigned char *resData; }; std::string DoCompress(CompressProps &props) { tjhandle handle = tjInitCompress(); if (handle == nullptr) { return tjGetErrorStr(); } int err = tjCompress2(handle, props.srcData, props.width, props.stride * props.bpp, props.height, props.format, &props.resData, &props.resSize, props.subsampling, props.quality, props.flags); if (err != 0) { tjDestroy(handle); return tjGetErrorStr(); } err = tjDestroy(handle); if (err != 0) { return tjGetErrorStr(); } if (props.resData == nullptr) { return "No output data"; } return ""; } Napi::Object CompressResult(const Napi::Env &env, const Napi::Buffer dstBuffer, const CompressProps &props) { Napi::Object res = Napi::Object::New(env); res.Set("data", dstBuffer); res.Set("size", props.resSize); return res; } class CompressWorker : public Napi::AsyncWorker { public: CompressWorker( Napi::Env &env, Napi::Buffer &srcBuffer, Napi::Buffer &dstBuffer, CompressProps &props) : AsyncWorker(env), deferred(Napi::Promise::Deferred::New(env)), srcBuffer(Napi::Reference>::New(srcBuffer, 1)), dstBuffer(Napi::Reference>::New(dstBuffer, 1)), props(props) { } ~CompressWorker() { this->srcBuffer.Reset(); this->dstBuffer.Reset(); } void Execute() { std::string err = DoCompress(this->props); if (!err.empty()) { SetError(err); } } void OnOK() { deferred.Resolve(CompressResult(Env(), this->dstBuffer.Value(), this->props)); } void OnError(Napi::Error const &error) { deferred.Reject(error.Value()); } Napi::Promise GetPromise() const { return deferred.Promise(); } private: Napi::Promise::Deferred deferred; Napi::Reference> srcBuffer; Napi::Reference> dstBuffer; CompressProps props; }; Napi::Value CompressInner(const Napi::CallbackInfo &info, bool async) { Napi::Env env = info.Env(); if (info.Length() < 2) { Napi::TypeError::New(env, "Not enough arguments") .ThrowAsJavaScriptException(); return env.Null(); } if (!info[0].IsBuffer()) { Napi::TypeError::New(env, "Invalid source buffer") .ThrowAsJavaScriptException(); return env.Null(); } Napi::Buffer srcBuffer = info[0].As>(); unsigned int offset = 0; Napi::Buffer dstBuffer; if (info[1].IsBuffer()) { dstBuffer = info[1].As>(); offset++; if (dstBuffer.Length() == 0) { Napi::TypeError::New(env, "Invalid destination buffer") .ThrowAsJavaScriptException(); return env.Null(); } } if (info.Length() < offset + 2 || !info[offset + 1].IsObject()) { Napi::TypeError::New(env, "Invalid options").ThrowAsJavaScriptException(); return env.Null(); } Napi::Object options = info[offset + 1].As(); BufferSizeOptions parsedOptions = ParseBufferSizeOptions(env, options); if (!parsedOptions.valid) { return env.Null(); } CompressProps props = {}; props.srcData = srcBuffer.Data(); props.width = parsedOptions.width; props.height = parsedOptions.height; props.subsampling = parsedOptions.subsampling; Napi::Value tmpFormat = options.Get("format"); if (!tmpFormat.IsNumber()) { Napi::TypeError::New(env, "Invalid format").ThrowAsJavaScriptException(); return env.Null(); } props.format = tmpFormat.As().Uint32Value(); // Figure out bpp from format (needed to calculate output buffer size) props.bpp = 0; switch (props.format) { case TJPF_GRAY: props.bpp = 1; break; case TJPF_RGB: case TJPF_BGR: props.bpp = 3; break; case TJPF_RGBX: case TJPF_BGRX: case TJPF_XRGB: case TJPF_XBGR: case TJPF_RGBA: case TJPF_BGRA: case TJPF_ABGR: case TJPF_ARGB: props.bpp = 4; break; default: Napi::TypeError::New(env, "Invalid input format").ThrowAsJavaScriptException(); return env.Null(); } props.stride = parsedOptions.width; Napi::Value tmpStride = options.Get("stride"); if (!tmpStride.IsUndefined()) { if (!tmpStride.IsNumber()) { Napi::TypeError::New(env, "Invalid stride").ThrowAsJavaScriptException(); return env.Null(); } props.stride = tmpStride.As().Uint32Value(); } props.quality = NJT_DEFAULT_QUALITY; Napi::Value tmpQuality = options.Get("quality"); if (!tmpQuality.IsUndefined()) { if (!tmpQuality.IsNumber()) { Napi::TypeError::New(env, "Invalid quality").ThrowAsJavaScriptException(); return env.Null(); } props.quality = tmpQuality.As().Uint32Value(); } if (props.quality <= 0 || props.quality > 100) { Napi::TypeError::New(env, "Invalid quality").ThrowAsJavaScriptException(); return env.Null(); } if (srcBuffer.Length() < static_cast(props.stride) * props.height * props.bpp) { Napi::TypeError::New(env, "Source data is not long enough").ThrowAsJavaScriptException(); return env.Null(); } uint32_t dstLength = tjBufSize(props.width, props.height, props.subsampling); if (dstBuffer.IsEmpty()) { dstBuffer = Napi::Buffer::New(env, dstLength); } if (dstLength > dstBuffer.Length()) { Napi::TypeError::New(env, "Insufficient output buffer").ThrowAsJavaScriptException(); return env.Null(); } props.flags = TJFLAG_FASTDCT | TJFLAG_NOREALLOC; props.resSize = dstBuffer.Length(); props.resData = dstBuffer.Data(); if (async) { CompressWorker *wk = new CompressWorker(env, srcBuffer, dstBuffer, props); wk->Queue(); return wk->GetPromise(); } else { std::string errStr = DoCompress(props); if (!errStr.empty()) { Napi::TypeError::New(env, errStr).ThrowAsJavaScriptException(); return env.Null(); } return CompressResult(env, dstBuffer, props); } } Napi::Value CompressAsync(const Napi::CallbackInfo &info) { return CompressInner(info, true); } Napi::Value CompressSync(const Napi::CallbackInfo &info) { return CompressInner(info, false); }