diff --git a/Tests/LibPDF/TestPDF.cpp b/Tests/LibPDF/TestPDF.cpp index 5045320b89..6b7a0060ec 100644 --- a/Tests/LibPDF/TestPDF.cpp +++ b/Tests/LibPDF/TestPDF.cpp @@ -137,6 +137,29 @@ TEST_CASE(sampled) EXPECT_EQ(MUST(f2->evaluate(Vector { 0.5f })), (Vector { 10.0f, 0.0f })); EXPECT_EQ(MUST(f2->evaluate(Vector { 0.75f })), (Vector { 5.0f, 4.0f })); EXPECT_EQ(MUST(f2->evaluate(Vector { 1.0f })), (Vector { 0.0f, 8.0f })); + + auto f3 = MUST(make_sampled_function(Vector { { 0, 255, 0, 255, 0, 255 } }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 10.0f }, { 3, 2 })); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.0f, 0.0f })), Vector { 0.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.25f, 0.0f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.5f, 0.0f })), Vector { 10.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.75f, 0.0f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 1.0f, 0.0f })), Vector { 0.0f }); + + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.0f, 0.5f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.25f, 0.5f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.5f, 0.5f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.75f, 0.5f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 1.0f, 0.5f })), Vector { 5.0f }); + + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.0f, 1.0f })), Vector { 10.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.25f, 1.0f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.5f, 1.0f })), Vector { 0.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 0.75f, 1.0f })), Vector { 5.0f }); + EXPECT_EQ(MUST(f3->evaluate(Vector { 1.0f, 1.0f })), Vector { 10.0f }); + + auto f4 = MUST(make_sampled_function(Vector { { 0, 255, 255, 0, 0, 255, 255, 0 } }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 10.0f, 0.0f, 8.0f }, { 2, 2 })); + EXPECT_EQ(MUST(f4->evaluate(Vector { 0.0f, 0.0f })), (Vector { 0.0f, 8.0f })); + EXPECT_EQ(MUST(f4->evaluate(Vector { 0.5f, 0.5f })), (Vector { 5.0f, 4.0f })); } static PDF::PDFErrorOr> make_postscript_function(StringView program, Vector domain, Vector range) diff --git a/Userland/Libraries/LibPDF/Function.cpp b/Userland/Libraries/LibPDF/Function.cpp index 246b1ca33a..6eab48b019 100644 --- a/Userland/Libraries/LibPDF/Function.cpp +++ b/Userland/Libraries/LibPDF/Function.cpp @@ -28,6 +28,17 @@ public: private: SampledFunction(NonnullRefPtr); + float sample(Vector const& coordinates, size_t r) const + { + size_t stride = 1; + size_t offset = 0; + for (size_t i = 0; i < coordinates.size(); ++i) { + offset += coordinates[i] * stride; + stride *= m_sizes[i]; + } + return m_sample_data[offset * m_range.size() + r]; + } + Vector m_domain; Vector m_range; @@ -46,6 +57,7 @@ private: NonnullRefPtr m_stream; ReadonlyBytes m_sample_data; + Vector mutable m_inputs; Vector mutable m_outputs; }; @@ -144,21 +156,19 @@ SampledFunction::create(Document* document, Vector domain, Optionalm_order = order; function->m_encode = move(encode); function->m_decode = move(decode); + function->m_inputs.resize(function->m_domain.size()); function->m_outputs.resize(function->m_range.size()); return function; } -PDFErrorOr> SampledFunction::evaluate(ReadonlySpan x) const +PDFErrorOr> SampledFunction::evaluate(ReadonlySpan xs) const { - if (x.size() != m_domain.size()) + if (xs.size() != m_domain.size()) return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" }; if (m_order != Order::Linear) return Error { Error::Type::RenderingUnsupported, "Sample function with cubic order not yet implemented" }; - if (m_domain.size() != 1) - return Error { Error::Type::RenderingUnsupported, "Sample function with m > 1 not yet implemented" }; - if (m_bits_per_sample != 8) return Error { Error::Type::RenderingUnsupported, "Sample function with bits per sample != 8 not yet implemented" }; @@ -166,25 +176,61 @@ PDFErrorOr> SampledFunction::evaluate(ReadonlySpan x) return y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min); }; - float xc = clamp(x[0], m_domain[0].lower, m_domain[0].upper); - float e = interpolate(xc, m_domain[0].lower, m_domain[0].upper, m_encode[0].lower, m_encode[0].upper); - float ec = clamp(e, 0.0f, static_cast(m_sizes[0] - 1)); - - float e0 = floor(ec); - float e1 = ceil(ec); - if (e0 == e1) { - if (e0 == 0.0f) - e1 = 1.0f; - else - e0 = e1 - 1.0f; + for (size_t i = 0; i < m_domain.size(); ++i) { + float x = clamp(xs[i], m_domain[i].lower, m_domain[i].upper); + float e = interpolate(x, m_domain[i].lower, m_domain[i].upper, m_encode[i].lower, m_encode[i].upper); + float ec = clamp(e, 0.0f, static_cast(m_sizes[i] - 1)); + m_inputs[i] = ec; } - size_t plane_size = m_range.size(); - for (size_t i = 0; i < m_range.size(); ++i) { - float s0 = m_sample_data[(size_t)e0 * plane_size + i]; - float s1 = m_sample_data[(size_t)e1 * plane_size + i]; - float r0 = interpolate(ec, e0, e1, s0, s1); - r0 = interpolate(r0, 0.0f, 255.0f, m_decode[i].lower, m_decode[i].upper); - m_outputs[i] = clamp(r0, m_range[i].lower, m_range[i].upper); + + auto get_bounds = [](float x, float& low, float& high) { + low = floorf(x); + high = ceilf(x); + if (low == high) { + if (low == 0.0f) + high = 1.0f; + else + low = high - 1.0f; + } + }; + + for (size_t r = 0; r < m_range.size(); ++r) { + // For 1-D input data, we need to sample 2 points, one to the left and one to the right, and then interpolate between them. + // For 2-D input data, we need to sample 4 points (top-left, top-right, bottom-left, bottom-right), + // then reduce them to 2 points by interpolating along y, and then to 1 by interpolating along x. + // For 3-D input data, it's 8 points in a cube around the point, then reduce to 4 points by interpolating along z, + // then 2 by interpolating along y, then 1 by interpolating along x. + // So for the general case, we create 2**N samples, and then for each coordinate, we cut the number of samples in half + // by interpolating along that coordinate. + Vector samples; + samples.resize(1 << m_domain.size()); + // The i'th bit of mask indicates if the i'th coordinate is rounded up or down. + Vector coordinates; + coordinates.resize(m_domain.size()); + for (size_t mask = 0; mask < (1u << m_domain.size()); ++mask) { + for (size_t i = 0; i < m_domain.size(); ++i) { + float ec = m_inputs[i]; + float e0, e1; + get_bounds(ec, e0, e1); + if ((mask & (1u << i)) != 0) + ec = e1; + else + ec = e0; + coordinates[i] = static_cast(ec); + } + samples[mask] = sample(coordinates, r); + } + + for (int i = static_cast(m_domain.size() - 1); i >= 0; --i) { + float ec = m_inputs[i]; + float e0, e1; + get_bounds(ec, e0, e1); + for (size_t mask = 0; mask < (1u << i); ++mask) + samples[mask] = interpolate(ec, e0, e1, samples[mask], samples[mask | (1 << i)]); + } + + float result = interpolate(samples[0], 0.0f, 255.0f, m_decode[r].lower, m_decode[r].upper); + m_outputs[r] = clamp(result, m_range[r].lower, m_range[r].upper); } return m_outputs;