mirror of
https://github.com/lovell/sharp.git
synced 2025-12-19 07:15:08 +01:00
Add private maxColourDistance for functional tests
Switch MSE-based tests to use it Remove experimental MSE-based compare API
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "composite.h"
|
||||
|
||||
const int STATS_SUM2_COLUMN = 3;
|
||||
|
||||
/*
|
||||
Compare images `actual` and `expected` and return mean squared error (MSE).
|
||||
*/
|
||||
int Compare(VipsObject *context, VipsImage *actual, VipsImage *expected, double *out) {
|
||||
if (actual->Bands != expected->Bands)
|
||||
return -1;
|
||||
|
||||
if (actual->Type != expected->Type)
|
||||
return -1;
|
||||
|
||||
VipsImage *actualPremultiplied;
|
||||
if (Premultiply(context, actual, &actualPremultiplied))
|
||||
return -1;
|
||||
vips_object_local(context, actualPremultiplied);
|
||||
|
||||
VipsImage *expectedPremultiplied;
|
||||
if (Premultiply(context, expected, &expectedPremultiplied))
|
||||
return -1;
|
||||
vips_object_local(context, expectedPremultiplied);
|
||||
|
||||
VipsImage *difference;
|
||||
if (vips_subtract(expectedPremultiplied, actualPremultiplied, &difference, NULL))
|
||||
return -1;
|
||||
vips_object_local(context, difference);
|
||||
|
||||
VipsImage *stats;
|
||||
if (vips_stats(difference, &stats, NULL))
|
||||
return -1;
|
||||
vips_object_local(context, stats);
|
||||
|
||||
double *statsData = (double*) stats->data;
|
||||
int numValues = actual->Xsize * actual->Ysize * actual->Bands;
|
||||
double sumOfSquares = statsData[STATS_SUM2_COLUMN];
|
||||
double meanSquaredError = sumOfSquares / numValues;
|
||||
|
||||
// Return a reference to the mean squared error:
|
||||
*out = meanSquaredError;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef SRC_COMPARE_INTERNAL_H_
|
||||
#define SRC_COMPARE_INTERNAL_H_
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int Compare(VipsObject *context, VipsImage *actual, VipsImage *expected, double *out);;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SRC_COMPARE_INTERNAL_H_
|
||||
186
src/compare.cc
186
src/compare.cc
@@ -1,186 +0,0 @@
|
||||
#include <node.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "compare-internal.h"
|
||||
|
||||
using v8::Boolean;
|
||||
using v8::Exception;
|
||||
using v8::Function;
|
||||
using v8::Handle;
|
||||
using v8::Local;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::InitImage;
|
||||
using sharp::counterQueue;
|
||||
|
||||
struct CompareBaton {
|
||||
// Input
|
||||
std::string fileIn1;
|
||||
std::string fileIn2;
|
||||
|
||||
// Output
|
||||
bool isEqual;
|
||||
float meanSquaredError;
|
||||
std::string err;
|
||||
std::string status;
|
||||
|
||||
CompareBaton():
|
||||
isEqual(false),
|
||||
meanSquaredError(0.0) {}
|
||||
};
|
||||
|
||||
class CompareWorker : public NanAsyncWorker {
|
||||
|
||||
public:
|
||||
CompareWorker(NanCallback *callback, CompareBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
||||
~CompareWorker() {}
|
||||
|
||||
void Execute() {
|
||||
// Decrement queued task counter
|
||||
g_atomic_int_dec_and_test(&counterQueue);
|
||||
|
||||
ImageType imageType1 = ImageType::UNKNOWN;
|
||||
ImageType imageType2 = ImageType::UNKNOWN;
|
||||
VipsImage *image1 = NULL;
|
||||
VipsImage *image2 = NULL;
|
||||
|
||||
// Create "hook" VipsObject to hang image references from
|
||||
hook = reinterpret_cast<VipsObject*>(vips_image_new());
|
||||
|
||||
// From files
|
||||
imageType1 = DetermineImageType(baton->fileIn1.c_str());
|
||||
imageType2 = DetermineImageType(baton->fileIn2.c_str());
|
||||
|
||||
if (imageType1 != ImageType::UNKNOWN) {
|
||||
image1 = InitImage(baton->fileIn1.c_str(), VIPS_ACCESS_RANDOM);
|
||||
if (image1 == NULL) {
|
||||
(baton->err).append("Input file 1 has corrupt header");
|
||||
imageType1 = ImageType::UNKNOWN;
|
||||
} else {
|
||||
vips_object_local(hook, image1);
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input file 1 is of an unsupported image format");
|
||||
}
|
||||
|
||||
if (imageType2 != ImageType::UNKNOWN) {
|
||||
image2 = InitImage(baton->fileIn2.c_str(), VIPS_ACCESS_RANDOM);
|
||||
if (image2 == NULL) {
|
||||
(baton->err).append("Input file 2 has corrupt header");
|
||||
imageType2 = ImageType::UNKNOWN;
|
||||
} else {
|
||||
vips_object_local(hook, image2);
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input file 2 is of an unsupported image format");
|
||||
}
|
||||
|
||||
if (image1 != NULL && imageType1 != ImageType::UNKNOWN && image2 != NULL && imageType2 != ImageType::UNKNOWN) {
|
||||
double meanSquaredError;
|
||||
|
||||
baton->meanSquaredError = -1.0;
|
||||
baton->isEqual = FALSE;
|
||||
|
||||
if (image1->Type != image2->Type) {
|
||||
baton->status = "mismatchedTypes";
|
||||
} else if (image1->Bands != image2->Bands) {
|
||||
baton->status = "mismatchedBands";
|
||||
} else if (image1->Xsize != image2->Xsize || image1->Ysize != image2->Ysize) {
|
||||
baton->status = "mismatchedDimensions";
|
||||
} else {
|
||||
if (Compare(hook, image1, image2, &meanSquaredError)) {
|
||||
(baton->err).append("Failed to compare two images");
|
||||
return Error();
|
||||
} else {
|
||||
baton->status = "success";
|
||||
baton->meanSquaredError = meanSquaredError;
|
||||
baton->isEqual = meanSquaredError == 0.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Error();
|
||||
}
|
||||
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void CleanUp() {
|
||||
// Clean up any dangling image references
|
||||
g_object_unref(hook);
|
||||
|
||||
// Clean up
|
||||
vips_error_clear();
|
||||
vips_thread_shutdown();
|
||||
}
|
||||
|
||||
void HandleOKCallback () {
|
||||
NanScope();
|
||||
|
||||
Handle<Value> argv[2] = { NanNull(), NanNull() };
|
||||
if (!baton->err.empty()) {
|
||||
// Error
|
||||
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
|
||||
} else {
|
||||
// Compare Object
|
||||
Local<Object> info = NanNew<Object>();
|
||||
info->Set(NanNew<String>("isEqual"), NanNew<Boolean>(baton->isEqual));
|
||||
info->Set(NanNew<String>("status"), NanNew<String>(baton->status));
|
||||
if (baton->meanSquaredError >= 0.0) {
|
||||
info->Set(NanNew<String>("meanSquaredError"), NanNew<Number>(baton->meanSquaredError));
|
||||
}
|
||||
argv[1] = info;
|
||||
}
|
||||
delete baton;
|
||||
|
||||
// Return to JavaScript
|
||||
callback->Call(2, argv);
|
||||
}
|
||||
|
||||
/*
|
||||
Copy then clear the error message.
|
||||
Unref all transitional images on the hook.
|
||||
Clear all thread-local data.
|
||||
*/
|
||||
void Error() {
|
||||
// Get libvips' error message
|
||||
(baton->err).append(vips_error_buffer());
|
||||
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
private:
|
||||
CompareBaton* baton;
|
||||
VipsObject *hook;
|
||||
};
|
||||
|
||||
/*
|
||||
compare(options, callback)
|
||||
*/
|
||||
NAN_METHOD(compare) {
|
||||
NanScope();
|
||||
|
||||
// V8 objects are converted to non-V8 types held in the baton struct
|
||||
CompareBaton *baton = new CompareBaton;
|
||||
Local<Object> options = args[0]->ToObject();
|
||||
|
||||
// Input filename
|
||||
baton->fileIn1 = *String::Utf8Value(options->Get(NanNew<String>("fileIn1"))->ToString());
|
||||
baton->fileIn2 = *String::Utf8Value(options->Get(NanNew<String>("fileIn2"))->ToString());
|
||||
|
||||
// Join queue for worker thread
|
||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
||||
NanAsyncQueueWorker(new CompareWorker(callback, baton));
|
||||
|
||||
// Increment queued task counter
|
||||
g_atomic_int_inc(&counterQueue);
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifndef SRC_COMPARE_H_
|
||||
#define SRC_COMPARE_H_
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(compare);
|
||||
|
||||
#endif // SRC_COMPARE_H_
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "compare.h"
|
||||
#include "metadata.h"
|
||||
#include "resize.h"
|
||||
#include "utilities.h"
|
||||
@@ -20,12 +19,12 @@ extern "C" void init(v8::Handle<v8::Object> target) {
|
||||
// Methods available to JavaScript
|
||||
NODE_SET_METHOD(target, "metadata", metadata);
|
||||
NODE_SET_METHOD(target, "resize", resize);
|
||||
NODE_SET_METHOD(target, "compare", compare);
|
||||
NODE_SET_METHOD(target, "cache", cache);
|
||||
NODE_SET_METHOD(target, "concurrency", concurrency);
|
||||
NODE_SET_METHOD(target, "counters", counters);
|
||||
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
||||
NODE_SET_METHOD(target, "format", format);
|
||||
NODE_SET_METHOD(target, "_maxColourDistance", _maxColourDistance);
|
||||
}
|
||||
|
||||
NODE_MODULE(sharp, init)
|
||||
|
||||
105
src/utilities.cc
105
src/utilities.cc
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "utilities.h"
|
||||
#include "composite.h"
|
||||
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
@@ -12,6 +13,10 @@ using v8::Number;
|
||||
using v8::String;
|
||||
using v8::Boolean;
|
||||
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::ImageType;
|
||||
using sharp::InitImage;
|
||||
using sharp::HasAlpha;
|
||||
using sharp::counterQueue;
|
||||
using sharp::counterProcess;
|
||||
|
||||
@@ -142,3 +147,103 @@ NAN_METHOD(format) {
|
||||
|
||||
NanReturnValue(format);
|
||||
}
|
||||
|
||||
/*
|
||||
Synchronous, internal-only method used by some of the functional tests.
|
||||
Calculates the maximum colour distance using the DE2000 algorithm
|
||||
between two images of the same dimensions and number of channels.
|
||||
*/
|
||||
NAN_METHOD(_maxColourDistance) {
|
||||
NanScope();
|
||||
|
||||
// Create "hook" VipsObject to hang image references from
|
||||
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
|
||||
|
||||
// Open input files
|
||||
VipsImage *image1 = NULL;
|
||||
ImageType imageType1 = DetermineImageType(*String::Utf8Value(args[0]));
|
||||
if (imageType1 != ImageType::UNKNOWN) {
|
||||
image1 = InitImage(*String::Utf8Value(args[0]), VIPS_ACCESS_SEQUENTIAL);
|
||||
if (image1 == NULL) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("Input file 1 has corrupt header");
|
||||
} else {
|
||||
vips_object_local(hook, image1);
|
||||
}
|
||||
} else {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("Input file 1 is of an unsupported image format");
|
||||
}
|
||||
VipsImage *image2 = NULL;
|
||||
ImageType imageType2 = DetermineImageType(*String::Utf8Value(args[1]));
|
||||
if (imageType2 != ImageType::UNKNOWN) {
|
||||
image2 = InitImage(*String::Utf8Value(args[1]), VIPS_ACCESS_SEQUENTIAL);
|
||||
if (image2 == NULL) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("Input file 2 has corrupt header");
|
||||
} else {
|
||||
vips_object_local(hook, image2);
|
||||
}
|
||||
} else {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("Input file 2 is of an unsupported image format");
|
||||
}
|
||||
|
||||
// Ensure same number of channels
|
||||
if (image1->Bands != image2->Bands) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("mismatchedBands");
|
||||
}
|
||||
// Ensure same dimensions
|
||||
if (image1->Xsize != image2->Xsize || image1->Ysize != image2->Ysize) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError("mismatchedDimensions");
|
||||
}
|
||||
|
||||
// Premultiply and remove alpha
|
||||
if (HasAlpha(image1)) {
|
||||
VipsImage *imagePremultiplied1;
|
||||
if (Premultiply(hook, image1, &imagePremultiplied1)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
vips_object_local(hook, imagePremultiplied1);
|
||||
VipsImage *imagePremultipliedNoAlpha1;
|
||||
if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, NULL)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
vips_object_local(hook, imagePremultipliedNoAlpha1);
|
||||
image1 = imagePremultipliedNoAlpha1;
|
||||
}
|
||||
if (HasAlpha(image2)) {
|
||||
VipsImage *imagePremultiplied2;
|
||||
if (Premultiply(hook, image2, &imagePremultiplied2)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
vips_object_local(hook, imagePremultiplied2);
|
||||
VipsImage *imagePremultipliedNoAlpha2;
|
||||
if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, NULL)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
vips_object_local(hook, imagePremultipliedNoAlpha2);
|
||||
image2 = imagePremultipliedNoAlpha2;
|
||||
}
|
||||
// Calculate colour distance
|
||||
VipsImage *difference;
|
||||
if (vips_dE00(image1, image2, &difference, NULL)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
vips_object_local(hook, difference);
|
||||
// Extract maximum distance
|
||||
double maxColourDistance;
|
||||
if (vips_max(difference, &maxColourDistance, NULL)) {
|
||||
g_object_unref(hook);
|
||||
return NanThrowError(vips_error_buffer());
|
||||
}
|
||||
g_object_unref(hook);
|
||||
NanReturnValue(maxColourDistance);
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@ NAN_METHOD(concurrency);
|
||||
NAN_METHOD(counters);
|
||||
NAN_METHOD(libvipsVersion);
|
||||
NAN_METHOD(format);
|
||||
NAN_METHOD(_maxColourDistance);
|
||||
|
||||
#endif // SRC_UTILITIES_H_
|
||||
|
||||
Reference in New Issue
Block a user