소스 검색

LibPDF: Add test for SampledFunction and fix bugs found by it

* SampledFunction now keeps the StreamObject it gets data from alive
  (doesn't matter too much in practice, but does matter in the test,
  where nothing else keeps the stream alive).

* If a sample is an integer, we would previously sample that value
  twice and then divide by zero when interpolating. Make sure to
  sample 1 unit apart.
Nico Weber 1 년 전
부모
커밋
ec739460e0
2개의 변경된 파일51개의 추가작업 그리고 5개의 파일을 삭제
  1. 35 3
      Tests/LibPDF/TestPDF.cpp
  2. 16 2
      Userland/Libraries/LibPDF/Function.cpp

+ 35 - 3
Tests/LibPDF/TestPDF.cpp

@@ -97,14 +97,16 @@ static PDF::Value make_array(Vector<float> floats)
     return PDF::Value { adopt_ref(*new PDF::ArrayObject(move(values))) };
 }
 
-static PDF::PDFErrorOr<NonnullRefPtr<PDF::Function>> make_postscript_function(StringView program, Vector<float> domain, Vector<float> range)
+static PDF::PDFErrorOr<NonnullRefPtr<PDF::Function>> make_function(int type, ReadonlyBytes data, Vector<float> domain, Vector<float> range, Function<void(HashMap<DeprecatedFlyString, PDF::Value>&)> extra_keys = nullptr)
 {
     HashMap<DeprecatedFlyString, PDF::Value> map;
-    map.set(PDF::CommonNames::FunctionType, PDF::Value { 4 });
+    map.set(PDF::CommonNames::FunctionType, PDF::Value { type });
     map.set(PDF::CommonNames::Domain, make_array(move(domain)));
     map.set(PDF::CommonNames::Range, make_array(move(range)));
+    if (extra_keys)
+        extra_keys(map);
     auto dict = adopt_ref(*new PDF::DictObject(move(map)));
-    auto stream = adopt_ref(*new PDF::StreamObject(dict, MUST(ByteBuffer::copy(program.bytes()))));
+    auto stream = adopt_ref(*new PDF::StreamObject(dict, MUST(ByteBuffer::copy(data))));
 
     // document isn't used for anything, but UBSan complains about a (harmless) method call on a null object without it.
     auto file = MUST(Core::MappedFile::map("linearized.pdf"sv));
@@ -112,6 +114,36 @@ static PDF::PDFErrorOr<NonnullRefPtr<PDF::Function>> make_postscript_function(St
     return PDF::Function::create(document, stream);
 }
 
+static PDF::PDFErrorOr<NonnullRefPtr<PDF::Function>> make_sampled_function(ReadonlyBytes data, Vector<float> domain, Vector<float> range, Vector<float> sizes)
+{
+    return make_function(0, data, move(domain), move(range), [&sizes](auto& map) {
+        map.set(PDF::CommonNames::Size, make_array(sizes));
+        map.set(PDF::CommonNames::BitsPerSample, PDF::Value { 8 });
+    });
+}
+
+TEST_CASE(sampled)
+{
+    auto f1 = MUST(make_sampled_function(Vector<u8> { { 0, 255, 0 } }, { 0.0f, 1.0f }, { 0.0f, 10.0f }, { 3 }));
+    EXPECT_EQ(MUST(f1->evaluate(Vector<float> { 0.0f })), Vector<float> { 0.0f });
+    EXPECT_EQ(MUST(f1->evaluate(Vector<float> { 0.25f })), Vector<float> { 5.0f });
+    EXPECT_EQ(MUST(f1->evaluate(Vector<float> { 0.5f })), Vector<float> { 10.0f });
+    EXPECT_EQ(MUST(f1->evaluate(Vector<float> { 0.75f })), Vector<float> { 5.0f });
+    EXPECT_EQ(MUST(f1->evaluate(Vector<float> { 1.0f })), Vector<float> { 0.0f });
+
+    auto f2 = MUST(make_sampled_function(Vector<u8> { { 0, 255, 0, 255, 0, 255 } }, { 0.0f, 1.0f }, { 0.0f, 10.0f, 0.0f, 8.0f }, { 3 }));
+    EXPECT_EQ(MUST(f2->evaluate(Vector<float> { 0.0f })), (Vector<float> { 0.0f, 8.0f }));
+    EXPECT_EQ(MUST(f2->evaluate(Vector<float> { 0.25f })), (Vector<float> { 5.0f, 4.0f }));
+    EXPECT_EQ(MUST(f2->evaluate(Vector<float> { 0.5f })), (Vector<float> { 10.0f, 0.0f }));
+    EXPECT_EQ(MUST(f2->evaluate(Vector<float> { 0.75f })), (Vector<float> { 5.0f, 4.0f }));
+    EXPECT_EQ(MUST(f2->evaluate(Vector<float> { 1.0f })), (Vector<float> { 0.0f, 8.0f }));
+}
+
+static PDF::PDFErrorOr<NonnullRefPtr<PDF::Function>> make_postscript_function(StringView program, Vector<float> domain, Vector<float> range)
+{
+    return make_function(4, program.bytes(), move(domain), move(range));
+}
+
 static NonnullRefPtr<PDF::Function> check_postscript_function(StringView program, Vector<float> domain, Vector<float> range)
 {
     auto function = make_postscript_function(program, move(domain), move(range));

+ 16 - 2
Userland/Libraries/LibPDF/Function.cpp

@@ -26,6 +26,8 @@ public:
     virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
 
 private:
+    SampledFunction(NonnullRefPtr<StreamObject>);
+
     Vector<Bound> m_domain;
     Vector<Bound> m_range;
 
@@ -41,11 +43,18 @@ private:
     Vector<Bound> m_encode;
     Vector<Bound> m_decode;
 
+    NonnullRefPtr<StreamObject> m_stream;
     ReadonlyBytes m_sample_data;
 
     Vector<float> mutable m_outputs;
 };
 
+SampledFunction::SampledFunction(NonnullRefPtr<StreamObject> stream)
+    : m_stream(move(stream))
+    , m_sample_data(m_stream->bytes())
+{
+}
+
 PDFErrorOr<NonnullRefPtr<SampledFunction>>
 SampledFunction::create(Document* document, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<StreamObject> stream)
 {
@@ -127,7 +136,7 @@ SampledFunction::create(Document* document, Vector<Bound> domain, Optional<Vecto
     if (stream->bytes().size() < ceil_div(total_bits, 8ull))
         return Error { Error::Type::MalformedPDF, "Function type 0 stream too small" };
 
-    auto function = adopt_ref(*new SampledFunction());
+    auto function = adopt_ref(*new SampledFunction(stream));
     function->m_domain = move(domain);
     function->m_range = move(range.value());
     function->m_sizes = move(sizes);
@@ -135,7 +144,6 @@ SampledFunction::create(Document* document, Vector<Bound> domain, Optional<Vecto
     function->m_order = order;
     function->m_encode = move(encode);
     function->m_decode = move(decode);
-    function->m_sample_data = stream->bytes();
     function->m_outputs.resize(function->m_range.size());
     return function;
 }
@@ -164,6 +172,12 @@ PDFErrorOr<ReadonlySpan<float>> SampledFunction::evaluate(ReadonlySpan<float> x)
 
     float e0 = floor(ec);
     float e1 = ceil(ec);
+    if (e0 == e1) {
+        if (e0 == 0.0f)
+            e1 = 1.0f;
+        else
+            e0 = e1 - 1.0f;
+    }
     size_t plane_size = m_sizes[0];
     for (size_t i = 0; i < m_range.size(); ++i) {
         float s0 = m_sample_data[(size_t)e0 + i * plane_size];