mirror of https://github.com/JakubMelka/PDF4QT.git
Stitching function unit tests
This commit is contained in:
parent
234b7c77f7
commit
214af0629d
|
@ -229,6 +229,74 @@ PDFFunctionPtr PDFFunction::createFunctionImpl(const PDFDocument* document, cons
|
|||
case 3:
|
||||
{
|
||||
// Stitching function
|
||||
std::vector<PDFReal> bounds = loader.readNumberArrayFromDictionary(dictionary, "Bounds");
|
||||
std::vector<PDFReal> encode = loader.readNumberArrayFromDictionary(dictionary, "Encode");
|
||||
|
||||
if (domain.size() != 2)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Stitching function can have only one input value."));
|
||||
}
|
||||
|
||||
if (dictionary->hasKey("Functions"))
|
||||
{
|
||||
const PDFObject& functions = document->getObject(dictionary->get("Functions"));
|
||||
if (functions.isArray())
|
||||
{
|
||||
const PDFArray* array = functions.getArray();
|
||||
if (array->getCount() != bounds.size() + 1)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Stitching function has different function count. Expected %1, actual %2.").arg(array->getCount()).arg(bounds.size() + 1));
|
||||
}
|
||||
|
||||
std::vector<PDFStitchingFunction::PartialFunction> partialFunctions;
|
||||
partialFunctions.resize(array->getCount());
|
||||
|
||||
if (encode.size() != partialFunctions.size() * 2)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Stitching function has invalid encode array. Expected %1 items, actual %2.").arg(partialFunctions.size() * 2).arg(encode.size()));
|
||||
}
|
||||
|
||||
std::vector<PDFReal> boundsAdjusted;
|
||||
boundsAdjusted.resize(bounds.size() + 2);
|
||||
boundsAdjusted.front() = domain.front();
|
||||
boundsAdjusted.back() = domain.back();
|
||||
std::copy(bounds.cbegin(), bounds.cend(), std::next(boundsAdjusted.begin()));
|
||||
|
||||
Q_ASSERT(boundsAdjusted.size() == partialFunctions.size() + 1);
|
||||
|
||||
uint32_t n = 0;
|
||||
for (size_t i = 0; i < partialFunctions.size(); ++i)
|
||||
{
|
||||
PDFStitchingFunction::PartialFunction& partialFunction = partialFunctions[i];
|
||||
partialFunction.function = createFunctionImpl(document, array->getItem(i), context);
|
||||
partialFunction.bound0 = boundsAdjusted[i];
|
||||
partialFunction.bound1 = boundsAdjusted[i + 1];
|
||||
partialFunction.encode0 = encode[2 * i];
|
||||
partialFunction.encode1 = encode[2 * i + 1];
|
||||
|
||||
const uint32_t nLocal = partialFunction.function->getOutputVariableCount();
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
n = nLocal;
|
||||
}
|
||||
else if (n != nLocal)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Functions in stitching function has different number of output variables."));
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<PDFStitchingFunction>(1, n, std::move(domain), std::move(range), std::move(partialFunctions));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Stitching function has invalid functions."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Stitching function hasn't functions array."));
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
|
@ -476,6 +544,21 @@ PDFFunction::FunctionResult PDFExponentialFunction::apply(PDFFunction::const_ite
|
|||
return true;
|
||||
}
|
||||
|
||||
PDFStitchingFunction::PDFStitchingFunction(uint32_t m, uint32_t n,
|
||||
std::vector<PDFReal>&& domain,
|
||||
std::vector<PDFReal>&& range,
|
||||
std::vector<PDFStitchingFunction::PartialFunction>&& partialFunctions) :
|
||||
PDFFunction(m, n, std::move(domain), std::move(range)),
|
||||
m_partialFunctions(std::move(partialFunctions))
|
||||
{
|
||||
Q_ASSERT(m == 1);
|
||||
}
|
||||
|
||||
PDFStitchingFunction::~PDFStitchingFunction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PDFFunction::FunctionResult PDFStitchingFunction::apply(const_iterator x_1,
|
||||
const_iterator x_m,
|
||||
iterator y_1,
|
||||
|
|
|
@ -70,6 +70,12 @@ public:
|
|||
QString errorMessage;
|
||||
};
|
||||
|
||||
/// Returns number of input variables
|
||||
inline uint32_t getInputVariableCount() const { return m_m; }
|
||||
|
||||
/// Returns number of output variables
|
||||
inline uint32_t getOutputVariableCount() const { return m_n; }
|
||||
|
||||
using iterator = PDFReal*;
|
||||
using const_iterator = const PDFReal*;
|
||||
|
||||
|
@ -112,7 +118,7 @@ protected:
|
|||
/// \param y_max End of the output interval
|
||||
static inline constexpr PDFReal interpolate(PDFReal x, PDFReal x_min, PDFReal x_max, PDFReal y_min, PDFReal y_max)
|
||||
{
|
||||
return y_min + x * (y_max - y_min) / (x_max - x_min);
|
||||
return y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min);
|
||||
}
|
||||
|
||||
/// Performs linear interpolation between c0 and c1 using x (in range [0.0, 1.0]). If x is not of this range,
|
||||
|
@ -215,7 +221,7 @@ private:
|
|||
/// is defined as f(x) = c0 + x^exponent * (c1 - c0). If exponent is 1.0, then linear interpolation
|
||||
/// is performed as f(x) = c0 * (1 - x) + x * c1. To be more precise, if exponent is nearly 1.0,
|
||||
/// then linear interpolation is used instead.
|
||||
class PDFExponentialFunction : public PDFFunction
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFExponentialFunction : public PDFFunction
|
||||
{
|
||||
public:
|
||||
/// Construct new exponential function.
|
||||
|
@ -252,7 +258,7 @@ private:
|
|||
/// Stitching function (Type 3 function)
|
||||
/// This type of function has always exactly one input. Transformation of this function
|
||||
/// is defined via k subfunctions which are used in defined intervals of the input value.
|
||||
class PDFStitchingFunction : public PDFFunction
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFStitchingFunction : public PDFFunction
|
||||
{
|
||||
public:
|
||||
struct PartialFunction
|
||||
|
|
|
@ -50,6 +50,7 @@ private slots:
|
|||
void test_lzw_filter();
|
||||
void test_sampled_function();
|
||||
void test_exponential_function();
|
||||
void test_stitching_function();
|
||||
|
||||
private:
|
||||
void scanWholeStream(const char* stream);
|
||||
|
@ -845,6 +846,117 @@ void LexicalAnalyzerTest::test_exponential_function()
|
|||
}, pdf::PDFParserException);
|
||||
}
|
||||
|
||||
void LexicalAnalyzerTest::test_stitching_function()
|
||||
{
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Bounds [ 0.5 ] "
|
||||
" /Encode [ 0 0.5 0.5 1.0 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
for (double value = -1.0; value <= 3.0; value += 0.01)
|
||||
{
|
||||
const double clampedValue = qBound(0.0, value, 1.0);
|
||||
const double expected = clampedValue < 0.5 ? clampedValue : clampedValue * clampedValue;
|
||||
|
||||
double actual = 0.0;
|
||||
QVERIFY(function->apply(&value, &value + 1, &actual, &actual + 1));
|
||||
QVERIFY(qFuzzyCompare(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Bounds [ 0.5 ] "
|
||||
" /Encode [ 0 0.5 0.5 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 ] "
|
||||
" /Bounds [ 0.5 ] "
|
||||
" /Encode [ 0 0.5 0.5 1.0 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Bounds [ 0.5 0.5 ] "
|
||||
" /Encode [ 0 0.5 0.5 1.0 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Encode [ 0 0.5 0.5 1.0 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 3 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Bounds [ 0.5 ] "
|
||||
" /Functions [ /Identity << /FunctionType 2 /Domain [ 0.5 1.0 ] /N 2.0 >> ] "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
}
|
||||
|
||||
void LexicalAnalyzerTest::scanWholeStream(const char* stream)
|
||||
{
|
||||
pdf::PDFLexicalAnalyzer analyzer(stream, stream + strlen(stream));
|
||||
|
|
Loading…
Reference in New Issue