feTurbulence is not rendered correctly on Retina display
[WebKit-https.git] / Source / WebCore / platform / graphics / filters / FETurbulence.cpp
index 0366539..4d0ded4 100644 (file)
@@ -5,6 +5,7 @@
  * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
  * Copyright (C) 2010 Renata Hodovan <reni@inf.u-szeged.hu>
  * Copyright (C) 2011 Gabor Loki <loki@webkit.org>
+ * Copyright (C) 2017-2018 Apple Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "FETurbulence.h"
 
 #include "Filter.h"
-#include <wtf/text/TextStream.h>
-
-#include <runtime/Uint8ClampedArray.h>
+#include <JavaScriptCore/Uint8ClampedArray.h>
 #include <wtf/MathExtras.h>
 #include <wtf/ParallelJobs.h>
+#include <wtf/text/TextStream.h>
 
 namespace WebCore {
 
@@ -178,112 +178,177 @@ void FETurbulence::initPaint(PaintingData& paintingData)
     }
 }
 
-inline void checkNoise(int& noiseValue, int limitValue, int newValue)
+FETurbulence::StitchData FETurbulence::computeStitching(IntSize tileSize, float& baseFrequencyX, float& baseFrequencyY) const
 {
-    if (noiseValue >= limitValue)
-        noiseValue -= newValue;
-    if (noiseValue >= limitValue - 1)
-        noiseValue -= newValue - 1;
+    if (!m_stitchTiles)
+        return { };
+
+    float tileWidth = tileSize.width();
+    float tileHeight = tileSize.height();
+    ASSERT(tileWidth > 0 && tileHeight > 0);
+
+    // When stitching tiled turbulence, the frequencies must be adjusted
+    // so that the tile borders will be continuous.
+    if (baseFrequencyX) {
+        float lowFrequency = floorf(tileWidth * baseFrequencyX) / tileWidth;
+        float highFrequency = ceilf(tileWidth * baseFrequencyX) / tileWidth;
+        // BaseFrequency should be non-negative according to the standard.
+        if (baseFrequencyX / lowFrequency < highFrequency / baseFrequencyX)
+            baseFrequencyX = lowFrequency;
+        else
+            baseFrequencyX = highFrequency;
+    }
+    if (baseFrequencyY) {
+        float lowFrequency = floorf(tileHeight * baseFrequencyY) / tileHeight;
+        float highFrequency = ceilf(tileHeight * baseFrequencyY) / tileHeight;
+        if (baseFrequencyY / lowFrequency < highFrequency / baseFrequencyY)
+            baseFrequencyY = lowFrequency;
+        else
+            baseFrequencyY = highFrequency;
+    }
+
+    StitchData stitchData;
+    stitchData.width = roundf(tileWidth * baseFrequencyX);
+    stitchData.wrapX = s_perlinNoise + stitchData.width;
+    stitchData.height = roundf(tileHeight * baseFrequencyY);
+    stitchData.wrapY = s_perlinNoise + stitchData.height;
+
+    return stitchData;
 }
 
-float FETurbulence::noise2D(int channel, const PaintingData& paintingData, StitchData& stitchData, const FloatPoint& noiseVector)
+// This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement.
+FloatComponents FETurbulence::noise2D(const PaintingData& paintingData, const StitchData& stitchData, const FloatPoint& noiseVector) const
 {
-    struct Noise {
-        int noisePositionIntegerValue;
-        float noisePositionFractionValue;
+    struct NoisePosition {
+        int index; // bx0, by0 in the spec text.
+        int nextIndex; // bx1, by1 in the spec text.
+        float fraction; // rx0, ry0 in the spec text.
 
-        Noise(float component)
+        NoisePosition(float component)
         {
+            //  t = vec[0] + PerlinN;
+            //  bx0 = (int)t;
+            //  bx1 = bx0+1;
+            //  rx0 = t - (int)t;
             float position = component + s_perlinNoise;
-            noisePositionIntegerValue = static_cast<int>(position);
-            noisePositionFractionValue = position - noisePositionIntegerValue;
+            index = static_cast<int>(position);
+            nextIndex = index + 1;
+            fraction = position - index;
+        }
+        
+        void stitch(int size, int wrapSize)
+        {
+            // if (bx0 >= pStitchInfo->nWrapX)
+            //   bx0 -= pStitchInfo->nWidth;
+            if (index >= wrapSize)
+                index -= size;
+
+            // if (bx1 >= pStitchInfo->nWrapX)
+            //   bx1 -= pStitchInfo->nWidth;
+            if (nextIndex >= wrapSize)
+                nextIndex -= size;
         }
     };
 
-    Noise noiseX(noiseVector.x());
-    Noise noiseY(noiseVector.y());
+    NoisePosition noiseX(noiseVector.x());
+    NoisePosition noiseY(noiseVector.y());
 
     // If stitching, adjust lattice points accordingly.
     if (m_stitchTiles) {
-        checkNoise(noiseX.noisePositionIntegerValue, stitchData.wrapX, stitchData.width);
-        checkNoise(noiseY.noisePositionIntegerValue, stitchData.wrapY, stitchData.height);
+        noiseX.stitch(stitchData.width, stitchData.wrapX);
+        noiseY.stitch(stitchData.height, stitchData.wrapY);
     }
 
-    noiseX.noisePositionIntegerValue &= s_blockMask;
-    noiseY.noisePositionIntegerValue &= s_blockMask;
-    int latticeIndex = paintingData.latticeSelector[noiseX.noisePositionIntegerValue];
-    int nextLatticeIndex = paintingData.latticeSelector[(noiseX.noisePositionIntegerValue + 1) & s_blockMask];
-
-    float sx = smoothCurve(noiseX.noisePositionFractionValue);
-    float sy = smoothCurve(noiseY.noisePositionFractionValue);
-    float a, b, u, v;
-
-    // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement.
-    int temp = paintingData.latticeSelector[latticeIndex + noiseY.noisePositionIntegerValue];
-    const float* q = paintingData.gradient[channel][temp];
-    u = noiseX.noisePositionFractionValue * q[0] + noiseY.noisePositionFractionValue * q[1];
-    temp = paintingData.latticeSelector[nextLatticeIndex + noiseY.noisePositionIntegerValue];
-    q = paintingData.gradient[channel][temp];
-    v = (noiseX.noisePositionFractionValue - 1) * q[0] + noiseY.noisePositionFractionValue * q[1];
-    a = linearInterpolation(sx, u, v);
-    temp = paintingData.latticeSelector[latticeIndex + noiseY.noisePositionIntegerValue + 1];
-    q = paintingData.gradient[channel][temp];
-    u = noiseX.noisePositionFractionValue * q[0] + (noiseY.noisePositionFractionValue - 1) * q[1];
-    temp = paintingData.latticeSelector[nextLatticeIndex + noiseY.noisePositionIntegerValue + 1];
-    q = paintingData.gradient[channel][temp];
-    v = (noiseX.noisePositionFractionValue - 1) * q[0] + (noiseY.noisePositionFractionValue - 1) * q[1];
-    b = linearInterpolation(sx, u, v);
-    return linearInterpolation(sy, a, b);
+    // bx0 &= BM;
+    // bx1 &= BM;
+    // by0 &= BM;
+    // by1 &= BM;
+    noiseX.index &= s_blockMask;
+    noiseX.nextIndex &= s_blockMask;
+    noiseY.index &= s_blockMask;
+    noiseY.nextIndex &= s_blockMask;
+
+    // i = uLatticeSelector[bx0];
+    // j = uLatticeSelector[bx1];
+    int latticeIndex = paintingData.latticeSelector[noiseX.index];
+    int nextLatticeIndex = paintingData.latticeSelector[noiseX.nextIndex];
+
+    // sx = double(s_curve(rx0));
+    // sy = double(s_curve(ry0));
+    float sx = smoothCurve(noiseX.fraction);
+    float sy = smoothCurve(noiseY.fraction);
+
+    auto noiseForChannel = [&](int channel) {
+        // b00 = uLatticeSelector[i + by0]
+        int b00 = paintingData.latticeSelector[latticeIndex + noiseY.index];
+        // q = fGradient[nColorChannel][b00]; u = rx0 * q[0] + ry0 * q[1];
+        const float* q = paintingData.gradient[channel][b00];
+        float u = noiseX.fraction * q[0] + noiseY.fraction * q[1];
+
+        // b10 = uLatticeSelector[j + by0];
+        int b10 = paintingData.latticeSelector[nextLatticeIndex + noiseY.index];
+        // rx1 = rx0 - 1.0f;
+        // q = fGradient[nColorChannel][b10]; v = rx1 * q[0] + ry0 * q[1];
+        q = paintingData.gradient[channel][b10];
+        float v = (noiseX.fraction - 1) * q[0] + noiseY.fraction * q[1];
+        // a = lerp(sx, u, v);
+        float a = linearInterpolation(sx, u, v);
+
+        // b01 = uLatticeSelector[i + by1];
+        int b01 = paintingData.latticeSelector[latticeIndex + noiseY.nextIndex];
+        // ry1 = ry0 - 1.0f;
+        // q = fGradient[nColorChannel][b01]; u = rx0 * q[0] + ry1 * q[1];
+        q = paintingData.gradient[channel][b01];
+        u = noiseX.fraction * q[0] + (noiseY.fraction - 1) * q[1];
+
+        // b11 = uLatticeSelector[j + by1];
+        int b11 = paintingData.latticeSelector[nextLatticeIndex + noiseY.nextIndex];
+        // q = fGradient[nColorChannel][b11]; v = rx1 * q[0] + ry1 * q[1];
+        q = paintingData.gradient[channel][b11];
+        v = (noiseX.fraction - 1) * q[0] + (noiseY.fraction - 1) * q[1];
+        // b = lerp(sx, u, v);
+        float b = linearInterpolation(sx, u, v);
+
+        // return lerp(sy, a, b);
+        return linearInterpolation(sy, a, b);
+    };
+
+    return {
+        noiseForChannel(0),
+        noiseForChannel(1),
+        noiseForChannel(2),
+        noiseForChannel(3)
+    };
 }
 
-unsigned char FETurbulence::calculateTurbulenceValueForPoint(int channel, const PaintingData& paintingData, StitchData& stitchData, const FloatPoint& point)
+// https://www.w3.org/TR/SVG/filters.html#feTurbulenceElement describes this conversion to color components.
+static inline ColorComponents toColorComponents(const FloatComponents& floatComponents)
 {
-    float tileWidth = paintingData.filterSize.width();
-    float tileHeight = paintingData.filterSize.height();
-    ASSERT(tileWidth > 0 && tileHeight > 0);
-    float baseFrequencyX = m_baseFrequencyX;
-    float baseFrequencyY = m_baseFrequencyY;
-    // Adjust the base frequencies if necessary for stitching.
-    if (m_stitchTiles) {
-        // When stitching tiled turbulence, the frequencies must be adjusted
-        // so that the tile borders will be continuous.
-        if (baseFrequencyX) {
-            float lowFrequency = floorf(tileWidth * baseFrequencyX) / tileWidth;
-            float highFrequency = ceilf(tileWidth * baseFrequencyX) / tileWidth;
-            // BaseFrequency should be non-negative according to the standard.
-            if (baseFrequencyX / lowFrequency < highFrequency / baseFrequencyX)
-                baseFrequencyX = lowFrequency;
-            else
-                baseFrequencyX = highFrequency;
-        }
-        if (baseFrequencyY) {
-            float lowFrequency = floorf(tileHeight * baseFrequencyY) / tileHeight;
-            float highFrequency = ceilf(tileHeight * baseFrequencyY) / tileHeight;
-            if (baseFrequencyY / lowFrequency < highFrequency / baseFrequencyY)
-                baseFrequencyY = lowFrequency;
-            else
-                baseFrequencyY = highFrequency;
-        }
-        // Set up TurbulenceInitial stitch values.
-        stitchData.width = roundf(tileWidth * baseFrequencyX);
-        stitchData.wrapX = s_perlinNoise + stitchData.width;
-        stitchData.height = roundf(tileHeight * baseFrequencyY);
-        stitchData.wrapY = s_perlinNoise + stitchData.height;
-    }
+    return {
+        std::max<uint8_t>(0, std::min(static_cast<int>(floatComponents.components[0] * 255), 255)),
+        std::max<uint8_t>(0, std::min(static_cast<int>(floatComponents.components[1] * 255), 255)),
+        std::max<uint8_t>(0, std::min(static_cast<int>(floatComponents.components[2] * 255), 255)),
+        std::max<uint8_t>(0, std::min(static_cast<int>(floatComponents.components[3] * 255), 255))
+    };
+}
 
-    float turbulenceFunctionResult = 0;
-    FloatPoint noiseVector(point.x() * baseFrequencyX, point.y() * baseFrequencyY);
+ColorComponents FETurbulence::calculateTurbulenceValueForPoint(const PaintingData& paintingData, StitchData stitchData, const FloatPoint& point) const
+{
+    FloatComponents turbulenceFunctionResult;
+    FloatPoint noiseVector(point.x() * paintingData.baseFrequencyX, point.y() * paintingData.baseFrequencyY);
     float ratio = 1;
     for (int octave = 0; octave < m_numOctaves; ++octave) {
-        if (m_type == FETURBULENCE_TYPE_FRACTALNOISE)
-            turbulenceFunctionResult += noise2D(channel, paintingData, stitchData, noiseVector) / ratio;
+        if (m_type == TurbulenceType::FractalNoise)
+            turbulenceFunctionResult += noise2D(paintingData, stitchData, noiseVector) / ratio;
         else
-            turbulenceFunctionResult += fabsf(noise2D(channel, paintingData, stitchData, noiseVector)) / ratio;
+            turbulenceFunctionResult += noise2D(paintingData, stitchData, noiseVector).abs() / ratio;
+
         noiseVector.setX(noiseVector.x() * 2);
         noiseVector.setY(noiseVector.y() * 2);
         ratio *= 2;
+
         if (m_stitchTiles) {
-            // Update stitch values. Subtracting s_perlinNoiseoise before the multiplication and
+            // Update stitch values. Subtracting s_perlinNoise before the multiplication and
             // adding it afterward simplifies to subtracting it once.
             stitchData.width *= 2;
             stitchData.wrapX = 2 * stitchData.wrapX - s_perlinNoise;
@@ -294,19 +359,20 @@ unsigned char FETurbulence::calculateTurbulenceValueForPoint(int channel, const
 
     // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult * 255) + 255) / 2 by fractalNoise
     // and (turbulenceFunctionResult * 255) by turbulence.
-    if (m_type == FETURBULENCE_TYPE_FRACTALNOISE)
+    if (m_type == TurbulenceType::FractalNoise)
         turbulenceFunctionResult = turbulenceFunctionResult * 0.5f + 0.5f;
-    // Clamp result
-    turbulenceFunctionResult = std::max(std::min(turbulenceFunctionResult, 1.f), 0.f);
-    return static_cast<unsigned char>(turbulenceFunctionResult * 255);
+
+    return toColorComponents(turbulenceFunctionResult);
 }
 
-void FETurbulence::fillRegion(Uint8ClampedArray* pixelArray, const PaintingData& paintingData, int startY, int endY)
+void FETurbulence::fillRegion(Uint8ClampedArray& pixelArray, const PaintingData& paintingData, StitchData stitchData, int startY, int endY) const
 {
+    ASSERT(endY > startY);
+
     IntRect filterRegion = absolutePaintRect();
+    filterRegion.scale(filter().filterScale());
     FloatPoint point(0, filterRegion.y() + startY);
     int indexOfPixelChannel = startY * (filterRegion.width() << 2);
-    StitchData stitchData;
     AffineTransform inverseTransfrom = filter().absoluteTransform().inverse().value_or(AffineTransform());
 
     for (int y = startY; y < endY; ++y) {
@@ -315,15 +381,16 @@ void FETurbulence::fillRegion(Uint8ClampedArray* pixelArray, const PaintingData&
         for (int x = 0; x < filterRegion.width(); ++x) {
             point.setX(point.x() + 1);
             FloatPoint localPoint = inverseTransfrom.mapPoint(point);
-            for (int channel = 0; channel < 4; ++channel, ++indexOfPixelChannel)
-                pixelArray->set(indexOfPixelChannel, calculateTurbulenceValueForPoint(channel, paintingData, stitchData, localPoint));
+            ColorComponents values = calculateTurbulenceValueForPoint(paintingData, stitchData, localPoint);
+            pixelArray.setRange(values.components, 4, indexOfPixelChannel);
+            indexOfPixelChannel += 4;
         }
     }
 }
 
 void FETurbulence::fillRegionWorker(FillRegionParameters* parameters)
 {
-    parameters->filter->fillRegion(parameters->pixelArray, *parameters->paintingData, parameters->startY, parameters->endY);
+    parameters->filter->fillRegion(*parameters->pixelArray, *parameters->paintingData, parameters->stitchData, parameters->startY, parameters->endY);
 }
 
 void FETurbulence::platformApplySoftware()
@@ -332,73 +399,84 @@ void FETurbulence::platformApplySoftware()
     if (!pixelArray)
         return;
 
-    if (absolutePaintRect().isEmpty()) {
+    IntSize resultSize(absolutePaintRect().size());
+    resultSize.scale(filter().filterScale());
+
+    if (resultSize.isEmpty()) {
         pixelArray->zeroFill();
         return;
     }
 
-    PaintingData paintingData(m_seed, roundedIntSize(filterPrimitiveSubregion().size()));
+    IntSize tileSize = roundedIntSize(filterPrimitiveSubregion().size());
+
+    float baseFrequencyX = m_baseFrequencyX;
+    float baseFrequencyY = m_baseFrequencyY;
+    StitchData stitchData = computeStitching(tileSize, baseFrequencyX, baseFrequencyY);
+
+    PaintingData paintingData(m_seed, tileSize, baseFrequencyX, baseFrequencyY);
     initPaint(paintingData);
 
-    int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension;
+    auto area = resultSize.area();
+    if (area.hasOverflowed())
+        return;
+
+    int height = resultSize.height();
+
+    unsigned maxNumThreads = height / 8;
+    unsigned optimalThreadNumber = std::min<unsigned>(area.unsafeGet() / s_minimalRectDimension, maxNumThreads);
     if (optimalThreadNumber > 1) {
-        // Initialize parallel jobs
         WTF::ParallelJobs<FillRegionParameters> parallelJobs(&WebCore::FETurbulence::fillRegionWorker, optimalThreadNumber);
 
         // Fill the parameter array
-        int i = parallelJobs.numberOfJobs();
-        if (i > 1) {
-            // Split the job into "stepY"-sized jobs but there a few jobs that need to be slightly larger since
-            // stepY * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
-            const int stepY = absolutePaintRect().height() / i;
-            const int jobsWithExtra = absolutePaintRect().height() % i;
-
-            int startY = 0;
-            for (; i > 0; --i) {
-                FillRegionParameters& params = parallelJobs.parameter(i-1);
+        auto numJobs = parallelJobs.numberOfJobs();
+        if (numJobs > 1) {
+            // Split the job into "stepY"-sized jobs, distributing the extra rows into the first "jobsWithExtra" jobs.
+            unsigned stepY = height / numJobs;
+            unsigned jobsWithExtra = height % numJobs;
+            unsigned startY = 0;
+
+            for (unsigned i = 0; i < numJobs; ++i) {
+                FillRegionParameters& params = parallelJobs.parameter(i);
                 params.filter = this;
                 params.pixelArray = pixelArray;
                 params.paintingData = &paintingData;
+                params.stitchData = stitchData;
                 params.startY = startY;
-                startY += i < jobsWithExtra ? stepY + 1 : stepY;
-                params.endY = startY;
+
+                unsigned jobHeight = (i < jobsWithExtra) ? stepY + 1 : stepY;
+                params.endY = params.startY + jobHeight;
+                startY += jobHeight;
             }
 
-            // Execute parallel jobs
             parallelJobs.execute();
             return;
         }
     }
 
     // Fallback to single threaded mode if there is no room for a new thread or the paint area is too small.
-    fillRegion(pixelArray, paintingData, 0, absolutePaintRect().height());
-}
-
-void FETurbulence::dump()
-{
+    fillRegion(*pixelArray, paintingData, stitchData, 0, height);
 }
 
-static TextStream& operator<<(TextStream& ts, const TurbulenceType& type)
+static TextStream& operator<<(TextStream& ts, TurbulenceType type)
 {
     switch (type) {
-    case FETURBULENCE_TYPE_UNKNOWN:
+    case TurbulenceType::Unknown:
         ts << "UNKNOWN";
         break;
-    case FETURBULENCE_TYPE_TURBULENCE:
-        ts << "TURBULANCE";
+    case TurbulenceType::Turbulence:
+        ts << "TURBULENCE";
         break;
-    case FETURBULENCE_TYPE_FRACTALNOISE:
+    case TurbulenceType::FractalNoise:
         ts << "NOISE";
         break;
     }
     return ts;
 }
 
-TextStream& FETurbulence::externalRepresentation(TextStream& ts, int indent) const
+TextStream& FETurbulence::externalRepresentation(TextStream& ts, RepresentationType representation) const
 {
-    writeIndent(ts, indent);
-    ts << "[feTurbulence";
-    FilterEffect::externalRepresentation(ts);
+    ts << indent << "[feTurbulence";
+    FilterEffect::externalRepresentation(ts, representation);
     ts << " type=\"" << type() << "\" "
        << "baseFrequency=\"" << baseFrequencyX() << ", " << baseFrequencyY() << "\" "
        << "seed=\"" << seed() << "\" "