diff --git a/src/libraw.ts b/src/libraw.ts index b817840..e3f0f86 100644 --- a/src/libraw.ts +++ b/src/libraw.ts @@ -42,6 +42,7 @@ interface LibRawWrapper { strerror: (errorCode: number) => string; unpack: () => number; unpack_thumb: () => number; + extract_tiff: (tiffPath: string) => number; version: () => string; versionNumber: () => number; } @@ -168,6 +169,10 @@ export class LibRaw { return this.accessLibRaw(() => this.libraw.unpack_thumb()); } + extract_tiff(tiffPath: string): Promise { + return this.accessLibRaw(() => this.libraw.extract_tiff(tiffPath)); + } + version(): Promise { return this.accessLibRaw(() => this.libraw.version()); } diff --git a/src/libraw_wrapper.cpp b/src/libraw_wrapper.cpp index 9a88d91..307c448 100644 --- a/src/libraw_wrapper.cpp +++ b/src/libraw_wrapper.cpp @@ -25,190 +25,220 @@ #include "wraptypes.h" #include -Napi::Object LibRawWrapper::Init(Napi::Env& env, Napi::Object& exports) { +Napi::Object LibRawWrapper::Init(Napi::Env &env, Napi::Object &exports) +{ Napi::HandleScope scope(env); Napi::Function func = - DefineClass( - env, - "LibRawWrapper", - { - InstanceMethod("getMetadata", &LibRawWrapper::GetMetadata), - InstanceMethod("getThumbnail", &LibRawWrapper::GetThumbnail), - InstanceMethod("getXmp", &LibRawWrapper::GetXmpData), - InstanceMethod("cameraCount", &LibRawWrapper::CameraCount), - InstanceMethod("cameraList", &LibRawWrapper::CameraList), - InstanceMethod("open_file", &LibRawWrapper::OpenFile), - InstanceMethod("open_buffer", &LibRawWrapper::OpenBuffer), - InstanceMethod("unpack", &LibRawWrapper::Unpack), - InstanceMethod("unpack_thumb", &LibRawWrapper::UnpackThumb), - InstanceMethod("recycle", &LibRawWrapper::Recycle), - InstanceMethod("error_count", &LibRawWrapper::ErrorCount), - InstanceMethod("recycle_datastream", &LibRawWrapper::RecycleDatastream), - InstanceMethod("strerror", &LibRawWrapper::StrError), - InstanceMethod("version", &LibRawWrapper::Version), - InstanceMethod("versionNumber", &LibRawWrapper::VersionNumber) - } - ); + DefineClass( + env, + "LibRawWrapper", + {InstanceMethod("getMetadata", &LibRawWrapper::GetMetadata), + InstanceMethod("getThumbnail", &LibRawWrapper::GetThumbnail), + InstanceMethod("getXmp", &LibRawWrapper::GetXmpData), + InstanceMethod("cameraCount", &LibRawWrapper::CameraCount), + InstanceMethod("cameraList", &LibRawWrapper::CameraList), + InstanceMethod("open_file", &LibRawWrapper::OpenFile), + InstanceMethod("open_buffer", &LibRawWrapper::OpenBuffer), + InstanceMethod("unpack", &LibRawWrapper::Unpack), + InstanceMethod("unpack_thumb", &LibRawWrapper::UnpackThumb), + InstanceMethod("extract_tiff", &LibRawWrapper::ExtractTiff), + InstanceMethod("recycle", &LibRawWrapper::Recycle), + InstanceMethod("error_count", &LibRawWrapper::ErrorCount), + InstanceMethod("recycle_datastream", &LibRawWrapper::RecycleDatastream), + InstanceMethod("strerror", &LibRawWrapper::StrError), + InstanceMethod("version", &LibRawWrapper::Version), + InstanceMethod("versionNumber", &LibRawWrapper::VersionNumber)}); exports.Set("LibRawWrapper", func); return exports; } -LibRawWrapper::LibRawWrapper(const Napi::CallbackInfo& info): Napi::ObjectWrap(info) { +LibRawWrapper::LibRawWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) +{ this->processor_ = new LibRaw(); } -Napi::Value LibRawWrapper::GetThumbnail(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::GetThumbnail(const Napi::CallbackInfo &info) +{ Napi::Env env = info.Env(); - + if (this->processor_->imgdata.thumbnail.thumb) { return Napi::Buffer::New( - env, - this->processor_->imgdata.thumbnail.thumb, - this->processor_->imgdata.thumbnail.tlength - ); + env, + this->processor_->imgdata.thumbnail.thumb, + this->processor_->imgdata.thumbnail.tlength); } Napi::Error::New( - env, - "Thumbnail is not unpacked or is null." - ).ThrowAsJavaScriptException(); + env, + "Thumbnail is not unpacked or is null.") + .ThrowAsJavaScriptException(); return env.Undefined(); } -Napi::Value LibRawWrapper::GetXmpData(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::GetXmpData(const Napi::CallbackInfo &info) +{ Napi::Env env = info.Env(); char *xmp = this->processor_->imgdata.idata.xmpdata; if (xmp) { return Napi::Buffer::New( - env, - xmp, - this->processor_->imgdata.idata.xmplen - ); + env, + xmp, + this->processor_->imgdata.idata.xmplen); } return Napi::Object::New(env); } -Napi::Value LibRawWrapper::GetMetadata(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::GetMetadata(const Napi::CallbackInfo &info) +{ Napi::Env env = info.Env(); libraw_data_t data = this->processor_->imgdata; return WrapLibRawData(&env, &data); } -Napi::Value LibRawWrapper::OpenFile(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::OpenFile(const Napi::CallbackInfo &info) +{ Napi::Env env = info.Env(); - if (!info[0].IsString()) { + if (!info[0].IsString()) + { Napi::TypeError::New(env, "openFile received an invalid argument, filename must be a string.").ThrowAsJavaScriptException(); } - if (info.Length() == 2 && !info[1].IsNumber()) { + if (info.Length() == 2 && !info[1].IsNumber()) + { Napi::TypeError::New(env, "openFile received an invalid argument, bigfile_size must be a number.").ThrowAsJavaScriptException(); } Napi::String filename = info[0].As(); int ret; - if (info.Length() == 2) { + if (info.Length() == 2) + { Napi::Number bigfile_size = info[1].As(); ret = this->processor_->open_file( - filename.Utf8Value().c_str(), - bigfile_size.Int64Value() - ); - } else { + filename.Utf8Value().c_str(), + bigfile_size.Int64Value()); + } + else + { ret = this->processor_->open_file(filename.Utf8Value().c_str()); } return Napi::Value::From(env, ret); } -Napi::Value LibRawWrapper::OpenBuffer(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::OpenBuffer(const Napi::CallbackInfo &info) +{ Napi::Env env = info.Env(); - if (info.Length() != 1 || !info[0].IsBuffer()) { + if (info.Length() != 1 || !info[0].IsBuffer()) + { Napi::TypeError::New(env, "openBuffer received a null argument, buffer is required.").ThrowAsJavaScriptException(); } Napi::Buffer buffer = info[0].As>(); return Napi::Value::From( - env, - this->processor_->open_buffer(buffer.Data(), buffer.Length()) - ); + env, + this->processor_->open_buffer(buffer.Data(), buffer.Length())); +} + +Napi::Value LibRawWrapper::Unpack(const Napi::CallbackInfo &info) +{ + return Napi::Value::From( + info.Env(), + this->processor_->unpack()); } -Napi::Value LibRawWrapper::Unpack(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::UnpackThumb(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->unpack() - ); + info.Env(), + this->processor_->unpack_thumb()); } -Napi::Value LibRawWrapper::UnpackThumb(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::ExtractTiff(const Napi::CallbackInfo &info) +{ + Napi::Env env = info.Env(); + if (!info[0].IsString()) + { + Napi::Error::New(env, "extract_tiff received an invalid argument, tiffpath must be a string.").ThrowAsJavaScriptException(); + } + + Napi::String filename = info[0].As(); + + this->processor_->imgdata.params.output_tiff = 1; // output as a tiff! + this->processor_->imgdata.params.use_camera_wb = 1; // use camera white balance + this->processor_->unpack(); + this->processor_->dcraw_process(); return Napi::Value::From( - info.Env(), - this->processor_->unpack_thumb() - ); + info.Env(), this->processor_->dcraw_ppm_tiff_writer(filename.Utf8Value().c_str())); } -void LibRawWrapper::Recycle(const Napi::CallbackInfo& info) { +void LibRawWrapper::Recycle(const Napi::CallbackInfo &info) +{ this->processor_->recycle(); } -Napi::Value LibRawWrapper::ErrorCount(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::ErrorCount(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->error_count() - ); + info.Env(), + this->processor_->error_count()); } -Napi::Value LibRawWrapper::VersionNumber(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::VersionNumber(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->versionNumber() - ); + info.Env(), + this->processor_->versionNumber()); } -Napi::Value LibRawWrapper::Version(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::Version(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->version() - ); + info.Env(), + this->processor_->version()); } -Napi::Value LibRawWrapper::CameraCount(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::CameraCount(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->cameraCount() - ); + info.Env(), + this->processor_->cameraCount()); } /* * `cameraList` provides a null-terminated list of camera names. * This function finds the limit and then allocates an Napi::Array with the * found limit, and loads the values to return. - * + * * This function can almost certainly be refactored for better efficiency and readability. */ -Napi::Value LibRawWrapper::CameraList(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::CameraList(const Napi::CallbackInfo &info) +{ const char **cameraList = this->processor_->cameraList(); size_t i = 0; - while (cameraList[i] != nullptr) { + while (cameraList[i] != nullptr) + { i++; } Napi::Array cameraListArray = Napi::Array::New(info.Env(), i); - for (size_t idx = 0; idx < i; idx++) { + for (size_t idx = 0; idx < i; idx++) + { cameraListArray[idx] = cameraList[idx]; } return cameraListArray; } -Napi::Value LibRawWrapper::StrError(const Napi::CallbackInfo& info) { +Napi::Value LibRawWrapper::StrError(const Napi::CallbackInfo &info) +{ return Napi::Value::From( - info.Env(), - this->processor_->strerror(info[0].As().Int32Value()) - ); + info.Env(), + this->processor_->strerror(info[0].As().Int32Value())); } -void LibRawWrapper::RecycleDatastream(const Napi::CallbackInfo& info) { +void LibRawWrapper::RecycleDatastream(const Napi::CallbackInfo &info) +{ this->processor_->recycle_datastream(); } diff --git a/src/libraw_wrapper.h b/src/libraw_wrapper.h index b8c49e6..4abbf9a 100644 --- a/src/libraw_wrapper.h +++ b/src/libraw_wrapper.h @@ -41,6 +41,7 @@ class LibRawWrapper: public Napi::ObjectWrap { Napi::Value Version(const Napi::CallbackInfo& info); Napi::Value VersionNumber(const Napi::CallbackInfo& info); Napi::Value StrError(const Napi::CallbackInfo& info); + Napi::Value ExtractTiff(const Napi::CallbackInfo &info); void RecycleDatastream(const Napi::CallbackInfo& info); void Recycle(const Napi::CallbackInfo& info); private: diff --git a/test/libraw.test.ts b/test/libraw.test.ts index 7add243..d3767d4 100644 --- a/test/libraw.test.ts +++ b/test/libraw.test.ts @@ -22,7 +22,7 @@ import { LibRaw } from '../src/libraw'; import path from 'path'; -import fs from 'fs'; +import fs, { promises as fsPromises } from 'fs'; import * as t from 'io-ts'; import { isRight } from 'fp-ts/Either'; @@ -39,6 +39,8 @@ const TEST_THUMBNAIL_JPG = path.join( 'test_images', 'test_thumb.jpg' ); +const SAMPLE_TIFF = path.join(__dirname, 'test_images', 'sample.tiff'); +const TEST_TIFF_OUTPUT_PATH = path.join(__dirname, 'test_images', 'test.tiff'); const __2dNumArray = t.array(t.array(t.number)); const __dngColor = t.array( @@ -340,6 +342,27 @@ describe('LibRaw', () => { }); }); + describe('extractTiff', () => { + test('throws error if there is an invalid path', async () => { + expect(await lr.openFile(RAW_SONY_FILE_PATH)).toBe(0); + // @ts-expect-error testing C++ error logic + await expect(lr.extract_tiff(0)).rejects.toThrow( + 'extract_tiff received an invalid argument, tiffpath must be a string.' + ); + }); + + test('extracts a tiff', async () => { + jest.setTimeout(20000); + expect(await lr.openFile(RAW_SONY_FILE_PATH)).toBe(0); + expect(await lr.extract_tiff(TEST_TIFF_OUTPUT_PATH)).toBe(0); + const [output, sample] = await Promise.all([ + fsPromises.readFile(TEST_TIFF_OUTPUT_PATH, { encoding: 'binary' }), + fsPromises.readFile(SAMPLE_TIFF, { encoding: 'binary' }), + ]); + expect(output).toEqual(sample); + }); + }); + describe('cameraCount', () => { test('gives the number of supported cameras', async () => { expect(await lr.cameraCount()).toBe(1117); diff --git a/test/test_images/sample.tiff b/test/test_images/sample.tiff new file mode 100644 index 0000000..0fb4900 Binary files /dev/null and b/test/test_images/sample.tiff differ diff --git a/test/test_images/test.tiff b/test/test_images/test.tiff new file mode 100644 index 0000000..0fb4900 Binary files /dev/null and b/test/test_images/test.tiff differ