diff --git a/qt5/src/ArthurOutputDev.cc b/qt5/src/ArthurOutputDev.cc index 34fde5690..9af32ae55 100644 --- a/qt5/src/ArthurOutputDev.cc +++ b/qt5/src/ArthurOutputDev.cc @@ -23,7 +23,7 @@ // Copyright (C) 2013 Thomas Freitag // Copyright (C) 2013 Dominik Haumann // Copyright (C) 2013 Mihai Niculescu -// Copyright (C) 2017 Oliver Sander +// Copyright (C) 2017, 2018 Oliver Sander // Copyright (C) 2017 Adrian Johnson // // To see a description of the changes please see the Changelog file that @@ -687,6 +687,132 @@ void ArthurOutputDev::eoFill(GfxState *state) m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::OddEvenFill ), m_currentBrush ); } +GBool ArthurOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) +{ + double x0, y0, x1, y1; + shading->getCoords(&x0, &y0, &x1, &y1); + + // get the clip region bbox + double xMin, yMin, xMax, yMax; + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + + // get the function domain + double t0 = shading->getDomain0(); + double t1 = shading->getDomain1(); + + // Max number of splits along the t axis + constexpr int maxSplits = 256; + + // Max delta allowed in any color component + const double colorDelta = (dblToCol(1 / 256.0)); + + // Number of color space components + auto nComps = shading->getColorSpace()->getNComps(); + + // Helper function to test two color objects for 'almost-equality' + auto isSameGfxColor = [&nComps,&colorDelta](const GfxColor &colorA, const GfxColor &colorB) + { + for (int k = 0; k < nComps; ++k) { + if (abs(colorA.c[k] - colorB.c[k]) > colorDelta) { + return false; + } + } + return true; + }; + + // Helper function: project a number into an interval + // With C++17 this is part of the standard library + auto clamp = [](double v, double lo, double hi) + { return std::min(std::max(v,lo), hi); }; + + // ta stores all parameter values where we evaluate the input shading function. + // In between, QLinearGradient will interpolate linearly. + // We set up the array with three values. + std::array ta; + ta[0] = tMin; + std::array next; + next[0] = maxSplits / 2; + ta[maxSplits / 2] = 0.5 * (tMin + tMax); + next[maxSplits / 2] = maxSplits; + ta[maxSplits] = tMax; + + // compute the color at t = tMin + double tt = clamp(t0 + (t1 - t0) * tMin, t0, t1); + + GfxColor color0, color1; + shading->getColor(tt, &color0); + + // Construct a gradient object and set its color at one parameter end + QLinearGradient gradient(QPointF(x0 + tMin * (x1 - x0), y0 + tMin * (y1 - y0)), + QPointF(x0 + tMax * (x1 - x0), y0 + tMax * (y1 - y0))); + + GfxRGB rgb; + shading->getColorSpace()->getRGB(&color0, &rgb); + QColor qColor(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b)); + gradient.setColorAt(0,qColor); + + // Look for more relevant parameter values by bisection + int i = 0; + while (i < maxSplits) { + + int j = next[i]; + while (j > i + 1) { + + // Next parameter value to try + tt = clamp(t0 + (t1 - t0) * ta[j], t0, t1); + shading->getColor(tt, &color1); + + // j is a good next color stop if the input shading can be approximated well + // on the interval (ta[i], ta[j]) by a linear interpolation. + // We test this by comparing the real color in the middle between ta[i] and ta[j] + // with the linear interpolant there. + auto midPoint = 0.5 * (ta[i] + ta[j]); + GfxColor colorAtMidPoint; + shading->getColor(midPoint, &colorAtMidPoint); + + GfxColor linearlyInterpolatedColor; + for (int ii=0; iigetColorSpace()->getRGB(&color1, &rgb); + qColor.setRgb(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b)); + gradient.setColorAt((ta[j] - tMin)/(tMax - tMin), qColor); + + // Move to the next parameter region + color0 = color1; + i = next[i]; + } + + state->moveTo(xMin, yMin); + state->lineTo(xMin, yMax); + state->lineTo(xMax, yMax); + state->lineTo(xMax, yMin); + state->closePath(); + + // Actually paint the shaded region + QBrush newBrush(gradient); + m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::WindingFill ), newBrush ); + + state->clearPath(); + + // True means: The shaded region has been painted + return gTrue; +} + void ArthurOutputDev::clip(GfxState *state) { m_painter.top()->setClipPath(convertPath( state, state->getPath(), Qt::WindingFill ), Qt::IntersectClip ); diff --git a/qt5/src/ArthurOutputDev.h b/qt5/src/ArthurOutputDev.h index b4ab3d7ec..701a883a8 100644 --- a/qt5/src/ArthurOutputDev.h +++ b/qt5/src/ArthurOutputDev.h @@ -20,7 +20,7 @@ // Copyright (C) 2011 Andreas Hartmetz // Copyright (C) 2013 Thomas Freitag // Copyright (C) 2013 Mihai Niculescu -// Copyright (C) 2017 Oliver Sander +// Copyright (C) 2017, 2018 Oliver Sander // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -83,6 +83,12 @@ class ArthurOutputDev: public OutputDev { // Does this device use drawChar() or drawString()? GBool useDrawChar() override { return gTrue; } + // Does this device implement shaded fills (aka gradients) natively? + // If this returns false, these shaded fills + // will be reduced to a series of other drawing operations. + // type==2 is 'axial shading' + GBool useShadedFills(int type) override { return type == 2; } + // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. GBool interpretType3Chars() override { return gTrue; } @@ -125,6 +131,7 @@ class ArthurOutputDev: public OutputDev { void stroke(GfxState *state) override; void fill(GfxState *state) override; void eoFill(GfxState *state) override; + GBool axialShadedFill(GfxState * state, GfxAxialShading *shading, double tMin, double tMax) override; //----- path clipping void clip(GfxState *state) override;