Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/libraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface LibRawWrapper {
strerror: (errorCode: number) => string;
unpack: () => number;
unpack_thumb: () => number;
extract_tiff: (tiffPath: string) => number;
version: () => string;
versionNumber: () => number;
}
Expand Down Expand Up @@ -168,6 +169,10 @@ export class LibRaw {
return this.accessLibRaw(() => this.libraw.unpack_thumb());
}

extract_tiff(tiffPath: string): Promise<number> {
return this.accessLibRaw(() => this.libraw.extract_tiff(tiffPath));
}

version(): Promise<string> {
return this.accessLibRaw(() => this.libraw.version());
}
Expand Down
200 changes: 115 additions & 85 deletions src/libraw_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,190 +25,220 @@
#include "wraptypes.h"
#include <fstream>

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<LibRawWrapper>(info) {
LibRawWrapper::LibRawWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<LibRawWrapper>(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<char>::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<char>::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<Napi::String>();
int ret;
if (info.Length() == 2) {
if (info.Length() == 2)
{
Napi::Number bigfile_size = info[1].As<Napi::Number>();
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<char> buffer = info[0].As<Napi::Buffer<char>>();
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<Napi::String>();

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<Napi::Number>().Int32Value())
);
info.Env(),
this->processor_->strerror(info[0].As<Napi::Number>().Int32Value()));
}

void LibRawWrapper::RecycleDatastream(const Napi::CallbackInfo& info) {
void LibRawWrapper::RecycleDatastream(const Napi::CallbackInfo &info)
{
this->processor_->recycle_datastream();
}

Expand Down
1 change: 1 addition & 0 deletions src/libraw_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class LibRawWrapper: public Napi::ObjectWrap<LibRawWrapper> {
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:
Expand Down
25 changes: 24 additions & 1 deletion test/libraw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
Binary file added test/test_images/sample.tiff
Binary file not shown.
Binary file added test/test_images/test.tiff
Binary file not shown.