Animate CSS Image filter() function
authorkrit@webkit.org <krit@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Aug 2013 19:29:03 +0000 (19:29 +0000)
committerkrit@webkit.org <krit@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Aug 2013 19:29:03 +0000 (19:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=119938

Reviewed by Simon Fraser.

Source/WebCore:

With this patch, the new introduced CSS Image function filter() can be
animated. According to the spec, just filter functions can be
interpolated.

The patch also prepares StyleImage blending for interpolation of other
generated images like gradients or cross-fade().

http://dev.w3.org/fxtf/filters/#interpolating-filter-image

Test: fast/filter-image/filter-image-animation.html

* css/CSSComputedStyleDeclaration.cpp: Reuse the code that creates a
    CSSValueList from ComputeStyle logic.
(WebCore::valueForPixel):
    For StyleRules we want to have not-adjusted length values.
(WebCore::ComputedStyleExtractor::valueForShadow):
    Add argument to switch between adjusted and not-adjusted length.
(WebCore::ComputedStyleExtractor::valueForFilter):
    Ditto.
(WebCore::ComputedStyleExtractor::propertyValue):
* css/CSSComputedStyleDeclaration.h:
* css/CSSFilterImageValue.h: Add helper functions
    for animating filters. We need to pass the FilterOperations for
    the image generation and the CSSValueList for StyleRule.
(WebCore::CSSFilterImageValue::filterOperations):
(WebCore::CSSFilterImageValue::setFilterOperations):
(WebCore::CSSFilterImageValue::cachedImage):
* page/animation/CSSPropertyAnimation.cpp:
    Add animation code to support animations between two filter()
    function values.
(WebCore::blendFilterOperations):
(WebCore::blendFunc):
(WebCore::filterBlend):
* rendering/style/StyleGeneratedImage.h: Add helper functions.
(WebCore::CSSFilterImageValue::imageValue):

LayoutTests:

Add tests to test animation between two filter() function values.
Furthermore, extended animation-test-helpers.js to parse all kind of CSS
image function where we support animations. CSS Image function can be
deeply nested as well now:

    -wekit-filter(-webkit-cross-fade(url(a.png), url(b.png), 50%), sepia(0.5))

Even the 50% can now be checked with a tolerance. If we should ever support
animations on nested CSS Images, the new code in animation-test-helpers.js
is prepared for it.

Fixed a bunch of tests that passed by accident or needed an update to the new
infrastructure.

* animations/resources/animation-test-helpers.js:
(parseCSSImage): For parsing of all kind of supported CSS Image functions.
    Currently supported: -webkit-cross-fade, -webkit-filter, url, none
    Still missing: linear and radial gradients (can not be animated yet).
    CSS Image functions are allowed to be nested as deep JS allows.
(parseCrossFade): Add parsing of input CSS images.
(parseFilterImage): Parse -webkit-filter image function as well as input images.
(parseFilterFunctionList): Parse filter function list. We now parse the
    function name as well. Added rudimentary support for drop-shadow and url.
(parseDeprecatedCustomFilterFunction): Special case old syntax of custom
    filter function. Shall be removed in the future.
(compareCSSImages): Compares all kind (even deep nested) CSS images.
(compareFilterFunctions): Now compare filter function names as well.
(comparePropertyValue): Use new compareCSSImages function.
* fast/filter-image/filter-image-animation-expected.txt: Added.
* fast/filter-image/filter-image-animation.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@154906 268f45cc-cd09-0410-ab3c-d52691b4dbfc

38 files changed:
LayoutTests/ChangeLog
LayoutTests/animations/cross-fade-background-image.html
LayoutTests/animations/cross-fade-border-image-source.html
LayoutTests/animations/cross-fade-list-style-image.html
LayoutTests/animations/cross-fade-webkit-mask-box-image.html
LayoutTests/animations/cross-fade-webkit-mask-image.html
LayoutTests/animations/resources/animation-test-helpers.js
LayoutTests/css3/filters/filter-animation-expected.txt
LayoutTests/css3/filters/filter-animation-from-none-expected.txt
LayoutTests/css3/filters/filter-animation-from-none-hw-expected.txt
LayoutTests/css3/filters/filter-animation-from-none-hw.html
LayoutTests/css3/filters/filter-animation-from-none-multi-expected.txt
LayoutTests/css3/filters/filter-animation-from-none-multi-hw-expected.txt
LayoutTests/css3/filters/filter-animation-from-none-multi-hw.html
LayoutTests/css3/filters/filter-animation-from-none-multi.html
LayoutTests/css3/filters/filter-animation-from-none.html
LayoutTests/css3/filters/filter-animation-hw-expected.txt
LayoutTests/css3/filters/filter-animation-hw.html
LayoutTests/css3/filters/filter-animation-multi-expected.txt
LayoutTests/css3/filters/filter-animation-multi-hw-expected.txt
LayoutTests/css3/filters/filter-animation-multi-hw.html
LayoutTests/css3/filters/filter-animation-multi.html
LayoutTests/css3/filters/filter-animation.html
LayoutTests/fast/filter-image/filter-image-animation-expected.txt [new file with mode: 0644]
LayoutTests/fast/filter-image/filter-image-animation.html [new file with mode: 0644]
LayoutTests/fast/filter-image/resources/blue.svg [new file with mode: 0644]
LayoutTests/platform/mac/animations/cross-fade-background-image-expected.txt
LayoutTests/platform/mac/animations/cross-fade-border-image-source-expected.txt
LayoutTests/platform/mac/animations/cross-fade-list-style-image-expected.txt
LayoutTests/platform/mac/animations/cross-fade-webkit-mask-box-image-expected.txt
LayoutTests/platform/mac/animations/cross-fade-webkit-mask-image-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/css/CSSComputedStyleDeclaration.cpp
Source/WebCore/css/CSSComputedStyleDeclaration.h
Source/WebCore/css/CSSFilterImageValue.cpp
Source/WebCore/css/CSSFilterImageValue.h
Source/WebCore/page/animation/CSSPropertyAnimation.cpp
Source/WebCore/rendering/style/StyleGeneratedImage.h

index 819fcde..45e9555 100644 (file)
@@ -1,3 +1,41 @@
+2013-08-30  Dirk Schulze  <krit@webkit.org>
+
+        Animate CSS Image filter() function
+        https://bugs.webkit.org/show_bug.cgi?id=119938
+
+        Reviewed by Simon Fraser.
+
+        Add tests to test animation between two filter() function values.
+        Furthermore, extended animation-test-helpers.js to parse all kind of CSS
+        image function where we support animations. CSS Image function can be
+        deeply nested as well now:
+
+            -wekit-filter(-webkit-cross-fade(url(a.png), url(b.png), 50%), sepia(0.5))
+
+        Even the 50% can now be checked with a tolerance. If we should ever support
+        animations on nested CSS Images, the new code in animation-test-helpers.js
+        is prepared for it.
+
+        Fixed a bunch of tests that passed by accident or needed an update to the new
+        infrastructure.
+
+        * animations/resources/animation-test-helpers.js:
+        (parseCSSImage): For parsing of all kind of supported CSS Image functions.
+            Currently supported: -webkit-cross-fade, -webkit-filter, url, none
+            Still missing: linear and radial gradients (can not be animated yet).
+            CSS Image functions are allowed to be nested as deep JS allows.
+        (parseCrossFade): Add parsing of input CSS images.
+        (parseFilterImage): Parse -webkit-filter image function as well as input images.
+        (parseFilterFunctionList): Parse filter function list. We now parse the
+            function name as well. Added rudimentary support for drop-shadow and url.
+        (parseDeprecatedCustomFilterFunction): Special case old syntax of custom
+            filter function. Shall be removed in the future.
+        (compareCSSImages): Compares all kind (even deep nested) CSS images.
+        (compareFilterFunctions): Now compare filter function names as well.
+        (comparePropertyValue): Use new compareCSSImages function.
+        * fast/filter-image/filter-image-animation-expected.txt: Added.
+        * fast/filter-image/filter-image-animation.html: Added.
+
 2013-08-30  Rob Buis  <rwlbuis@webkit.org>
 
         SVG error parsing empty path
index 595ed55..af0bbda 100644 (file)
@@ -41,9 +41,9 @@
   <script type="text/javascript" charset="utf-8">
     const expectedValues = [
       // [animation-name, time, element-id, property, expected-value, tolerance]
-      ["anim", 2.5, "box", "backgroundImage", 0.5, 0.05],
-      ["anim", 2.5, ["box", "static:boxStatic"], "backgroundImage", 0.5, 0.05],
-      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "backgroundImage", 0.5, 0.05],
+      ["anim", 2.5, "box", "backgroundImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
+      ["anim", 2.5, ["box", "static:boxStatic"], "backgroundImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
+      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "backgroundImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
     ];
 
     var doPixelTest = true;
index 4e68a4b..49ef54e 100644 (file)
@@ -43,9 +43,9 @@
     const expectedValues = [
       // [animation-name, time, element-id, property, expected-value, tolerance]
       // FIXME: We can't test reading the borderImage shorthand because of bug #13658.
-      ["anim", 2.5, "box", "borderImageSource", 0.5, 0.05],
-      ["anim", 2.5, ["box", "static:boxStatic"], "borderImageSource", 0.5, 0.05],
-      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "borderImageSource", 0.5, 0.05],
+      ["anim", 2.5, "box", "borderImageSource", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 0.5)", 0.05],
+      ["anim", 2.5, ["box", "static:boxStatic"], "borderImageSource", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 50%)", 0.05],
+      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "borderImageSource", "-webkit-cross-fade(url(stripes-100), url(green-100.png), 50%)", 0.05],
     ];
 
     var doPixelTest = true;
index 8610af2..80ff8f9 100644 (file)
@@ -38,9 +38,9 @@
   <script type="text/javascript" charset="utf-8">
     const expectedValues = [
       // [animation-name, time, element-id, property, expected-value, tolerance]
-      ["anim", 2.5, "box", "listStyleImage", 0.5, 0.05],
-      ["anim", 2.5, ["box", "static:boxStatic"], "listStyleImage", 0.5, 0.05],
-      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "listStyleImage", 0.5, 0.05],
+      ["anim", 2.5, "box", "listStyleImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
+      ["anim", 2.5, ["box", "static:boxStatic"], "listStyleImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
+      ["animShorthand", 2.5, ["boxShorthand", "static:boxStatic"], "listStyleImage", "-webkit-cross-fade(url(blue-100.png), url(green-100.png), 50%)", 0.05],
     ];
 
     var doPixelTest = true;
index 4295dae..b7286dc 100644 (file)
@@ -28,8 +28,8 @@
   <script type="text/javascript" charset="utf-8">
     const expectedValues = [
       // [animation-name, time, element-id, property, expected-value, tolerance]
-      ["anim", 2.25, "box", "webkitMaskBoxImage", 0.25, 0.05],
-      ["anim", 2.25, ["box", "static:boxStatic"], "webkitMaskBoxImage", 0.25, 0.05]
+      ["anim", 2.25, "box", "webkitMaskBoxImage", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 25%)", 0.05],
+      ["anim", 2.25, ["box", "static:boxStatic"], "webkitMaskBoxImage", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 25%)", 0.05]
     ];
 
     var doPixelTest = true;
index aa70ff4..35c7823 100644 (file)
@@ -28,8 +28,8 @@
   <script type="text/javascript" charset="utf-8">
     const expectedValues = [
       // [animation-name, time, element-id, property, expected-value, tolerance]
-      ["anim", 2.25, "box", "webkitMaskImage", 0.25, 0.05],
-      ["anim", 2.25, ["box", "static:boxStatic"], "webkitMaskImage", 0.25, 0.05]
+      ["anim", 2.25, "box", "webkitMaskImage", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 25%)", 0.05],
+      ["anim", 2.25, ["box", "static:boxStatic"], "webkitMaskImage", "-webkit-cross-fade(url(stripes-100.png), url(green-100.png), 25%)", 0.05]
     ];
 
     var doPixelTest = true;
index 1a97ad8..c810c8c 100644 (file)
@@ -34,9 +34,11 @@ Function parameters:
 
 */
 
-function isCloseEnough(actual, desired, tolerance)
+function isCloseEnough(actual, expected, tolerance)
 {
-    var diff = Math.abs(actual - desired);
+    if (isNaN(actual) || isNaN(expected))
+        return false;
+    var diff = Math.abs(actual - expected);
     return diff <= tolerance;
 }
 
@@ -49,14 +51,157 @@ function matrixStringToArray(s)
     return m[0].split(",");
 }
 
+function parseCSSImage(s)
+{
+    // Special case none.
+    if (s == "none")
+        return ["none"];
+
+    // Separate function name from function value.
+    var matches = s.match("([\\w\\-]+)\\((.*)\\)");
+    if (!matches){
+        console.error("Parsing error. Value not a CSS Image function ", s);
+        return false;
+    }
+
+    var functionName = matches[1];
+    var functionValue = matches[2];
+
+    // Generator functions can have CSS images as values themself.
+    // These functions will call parseCSSImage for each CSS Image.
+    switch (functionName) {
+    case "-webkit-filter":
+        return parseFilterImage(functionValue);
+    case "-webkit-cross-fade":
+        return parseCrossFade(functionValue);
+    case "url":
+        return ["url", functionValue];
+    // FIXME: Add support for linear and redial gradient.
+    default:
+        // All supported filter functions must be listed above.
+        return false;
+    }
+}
+
+// This should just be called by parseCSSImage.
 function parseCrossFade(s)
 {
-    var matches = s.match("-webkit-cross-fade\\((.*)\\s*,\\s*(.*)\\s*,\\s*(.*)\\)");
+    var matches = s.match("(.*)\\s*,\\s*(.*)\\s*,\\s*(.*)\\s*");
+    if (!matches) {
+        console.error("Parsing error on '-webkit-cross-fade()'.");
+        return null;
+    }
 
-    if (!matches)
+    var from = parseCSSImage(matches[1]);
+    var to = parseCSSImage(matches[2]);
+    if (!from || !to) {
+        console.error("Parsing error on images passed to '-webkit-cross-fade()' ", s);
+        return null;
+    }
+
+    var fadeValue = matches[3];
+    var percent;
+    if (isNaN(fadeValue)) {
+        // Check if last char is '%' and rip it off.
+        // Normalize it to number.
+        if (fadeValue.search('%') != fadeValue.length - 1) {
+            console.error("Passed value to '-webkit-cross-fade()' is not a number or percentage ", fadeValue);
+            return null;
+        }
+        fadeValue = fadeValue.slice(0, fadeValue.length - 1);
+        if (isNaN(fadeValue)) {
+            console.error("Passed value to '-webkit-cross-fade()' is not a number or percentage ", fadeValue);
+            return null;
+        }
+        percent = parseFloat(fadeValue) / 100;
+    } else
+        percent = parseFloat(fadeValue);
+
+    return ["-webkit-cross-fade", from, to, percent];
+}
+
+// This should just be called by parseCSSImage.
+function parseFilterImage(s)
+{
+    // Separate image value from filter function list.
+    var matches = s.match("([\\-\\w]+\\(.*\\))\\s*,\\s*(.*)\\s*");
+    if (!matches) {
+        console.error("Parsing error on 'fitler()' ", s);
+        return false;
+    }
+
+    var image = parseCSSImage(matches[1]);
+    if (!image) {
+        console.error("Parsing error on image passed to 'fitler()' ", s);
+        return false;
+    }
+
+    var filterFunctionList = parseFilterFunctionList(matches[2]);
+    if (!filterFunctionList) {
+        console.error("Parsing error on filter function list passed to 'fitler()' ", s);
+        return false;
+    }
+
+    return ["-webkit-filter", image, filterFunctionList];
+}
+
+function parseFilterFunctionList(s)
+{
+    var reg = /\)*\s*(blur|brightness|contrast|custom|drop\-shadow|grayscale|hue\-rotate|invert|opacity|saturate|sepia|url)\(/
+    var matches = s.split(reg);
+
+    // First item must be empty. All other items are of functionName, functionValue.
+    if (!matches || matches.shift() != "")
+        return null;
+
+    // RegEx above can not handle deprecated custom() function
+    var customPos = matches.indexOf("custom");
+    if (customPos >= 0 && matches[customPos+1] === "")
+        return parseDeprecatedCustomFilterFunction(s);
+
+    // Odd items are the function name, even items the function value.
+    if (!matches.length || matches.length % 2)
+        return null;
+
+    var functionList = [];
+    for (var i = 0; i < matches.length; i += 2) {
+        var functionName = matches[i];
+        var functionValue = matches[i+1];
+        functionList.push(functionName);
+        if (functionName == "drop-shadow" || functionName == "url") {
+            // FIXME: Support parsing of drop-shadow.
+            functionList.push(functionValue);
+            continue;
+        } else if (functionName == "custom") {
+            var filterParams;
+            if (!window.getCustomFilterParameters)
+                throw new Error("getCustomFilterParameters not found. Did you include custom-filter-parser.js?");
+            var filterParams = getCustomFilterParameters(functionValue);
+            if (!filterParams) {
+                console.error("Error on parsing custom filter parameters ", functionValue);
+                return null;
+            }
+            functionList.push(filterParams);
+            continue;
+        }
+        functionList.push(parseFloat(functionValue));
+    }
+    return functionList;
+}
+
+// FIXME: Remove function and caller when we removed the deprecated syntax of
+// the custom filter function.
+function parseDeprecatedCustomFilterFunction(s)
+{
+    var filterResult = s.match(/(\w+)\((.+)\)/);
+    var filterParams = filterResult[2];
+
+    if (filterResult[1] != "custom")
         return null;
 
-    return {"from": matches[1], "to": matches[2], "percent": parseFloat(matches[3])}
+    if (!window.getCustomFilterParameters)
+        throw new Error("getCustomFilterParameters not found. Did you include custom-filter-parser.js?");
+    return ["custom", getCustomFilterParameters(filterParams)];
 }
 
 function parseBasicShape(s)
@@ -99,6 +244,66 @@ function parseBasicShape(s)
     return {"shape": shapeFunction[1], "params": matches};
 }
 
+function compareCSSImages(computedValue, expectedValue, tolerance)
+{
+    var actual = typeof computedValue === "string" ? parseCSSImage(computedValue) : computedValue;
+    var expected = typeof expectedValue === "string" ? parseCSSImage(expectedValue) : expectedValue;
+    if (!actual || !Array.isArray(actual)         // Check that: actual is an array,
+        || !expected || !Array.isArray(expected)  // expected is an array,
+        || actual[0] != expected[0]) {            // and image function names are the same.
+        console.error("Unexpected mismatch between CSS Image functions.");
+        return false;
+    }
+    switch (actual[0]) {
+    case "none":
+        return true;
+    case "-webkit-filter":
+        return compareCSSImages(actual[1], expected[1], tolerance)
+            && compareFilterFunctions(actual[2], expected[2], tolerance);
+    case "-webkit-cross-fade":
+        return compareCSSImages(actual[1], expected[1], tolerance)
+            && compareCSSImages(actual[2], expected[2], tolerance)
+            && isCloseEnough(actual[3], expected[3], tolerance);
+    case "url":
+        return actual[1].search(expected[1]) >= 0;
+    default:
+        console.error("Unknown CSS Image function ", actual[0]);
+        return false;
+    }
+}
+
+// Called by CSS Image function filter() as well as filter property.
+function compareFilterFunctions(computedValue, expectedValue, tolerance)
+{
+    var actual = typeof computedValue === "string" ? parseFilterFunctionList(computedValue) : computedValue;
+    var expected = typeof expectedValue === "string" ? parseFilterFunctionList(expectedValue) : expectedValue;
+    if (!actual || !Array.isArray(actual)         // Check that: actual is an array,
+        || !expected || !Array.isArray(expected)  // expected is an array,
+        || !actual.length                         // actual array has entries,
+        || actual.length != expected.length       // actual and expected have same length
+        || actual.length % 2 == 1)                // and image function names are the same.
+        return false;
+
+    for (var i = 0; i < actual.length; i += 2) {
+        if (actual[i] != expected[i]) {
+            console.error("Filter functions do not match.");
+            return false;
+        }
+        if (actual[i] == "custom") {
+            if (!customFilterParameterMatch(actual[i+1], expected[i+1], tolerance)) {
+                console.error("Custom filter parameters do not match.");
+                return false;
+            }
+            continue;
+        }
+        if (!isCloseEnough(actual[i+1], expected[i+1], tolerance)) {
+            console.error("Filter function values do not match.");
+            return false;
+        }
+    }
+    return true;
+}
+
 function basicShapeParametersMatch(paramList1, paramList2, tolerance)
 {
     if (paramList1.shape != paramList2.shape
@@ -114,31 +319,6 @@ function basicShapeParametersMatch(paramList1, paramList2, tolerance)
     return true;
 }
 
-// Return an array of numeric filter params in 0-1.
-function getFilterParameters(s)
-{
-    var filterResult = s.match(/(\w+)\((.+)\)/);
-    if (!filterResult)
-        throw new Error("There's no filter in \"" + s + "\"");
-    var filterParams = filterResult[2];
-    if (filterResult[1] == "custom") {
-        if (!window.getCustomFilterParameters)
-            throw new Error("getCustomFilterParameters not found. Did you include custom-filter-parser.js?");
-        return getCustomFilterParameters(filterParams);
-    }
-    var paramList = filterParams.split(' '); // FIXME: the spec may allow comma separation at some point.
-    
-    // Normalize percentage values.
-    for (var i = 0; i < paramList.length; ++i) {
-        var param = paramList[i];
-        paramList[i] = parseFloat(paramList[i]);
-        if (param.indexOf('%') != -1)
-            paramList[i] = paramList[i] / 100;
-    }
-
-    return paramList;
-}
-
 function customFilterParameterMatch(param1, param2, tolerance)
 {
     if (param1.type != "parameter") {
@@ -182,26 +362,6 @@ function customFilterParameterMatch(param1, param2, tolerance)
     return true;
 }
 
-function filterParametersMatch(paramList1, paramList2, tolerance)
-{
-    if (paramList1.length != paramList2.length)
-        return false;
-    for (var i = 0; i < paramList1.length; ++i) {
-        var param1 = paramList1[i], 
-            param2 = paramList2[i];
-        if (typeof param1 == "object") {
-            // This is a custom filter parameter.
-            if (!customFilterParameterMatch(param1, param2, tolerance))
-                return false;
-            continue;
-        }
-        var match = isCloseEnough(param1, param2, tolerance);
-        if (!match)
-            return false;
-    }
-    return true;
-}
-
 function checkExpectedValue(expected, index)
 {
     var animationName = expected[index][0];
@@ -329,9 +489,9 @@ function comparePropertyValue(property, computedValue, expectedValue, tolerance)
             }
         }
     } else if (property == "webkitFilter") {
-        var filterParameters = getFilterParameters(computedValue);
-        var filter2Parameters = getFilterParameters(expectedValue);
-        result = filterParametersMatch(filterParameters, filter2Parameters, tolerance);
+        var filterParameters = parseFilterFunctionList(computedValue);
+        var filter2Parameters = parseFilterFunctionList(expectedValue);
+        result = compareFilterFunctions(filterParameters, filter2Parameters, tolerance);
     } else if (property == "webkitClipPath" || property == "webkitShapeInside") {
         var clipPathParameters = parseBasicShape(computedValue);
         var clipPathParameters2 = parseBasicShape(expectedValue);
@@ -342,20 +502,9 @@ function comparePropertyValue(property, computedValue, expectedValue, tolerance)
                || property == "borderImageSource"
                || property == "listStyleImage"
                || property == "webkitMaskImage"
-               || property == "webkitMaskBoxImage") {
-        var computedCrossFade = parseCrossFade(computedValue);
-
-        if (!computedCrossFade) {
-            result = false;
-        } else {
-            if (typeof expectedValue == "string") {
-                var computedCrossFade2 = parseCrossFade(expectedValue);
-                result = isCloseEnough(computedCrossFade.percent, computedCrossFade2.percent, tolerance) && computedCrossFade.from == computedCrossFade2.from && computedCrossFade.to == computedCrossFade2.to;
-            } else {
-                result = isCloseEnough(computedCrossFade.percent, expectedValue, tolerance)
-            }
-        }
-    } else {
+               || property == "webkitMaskBoxImage")
+        result = compareCSSImages(computedValue, expectedValue, tolerance);
+    else {
         result = isCloseEnough(computedValue, expectedValue, tolerance);
     }
     return result;
index 365f074..70a4591 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 1s saw something close to: grayscale(0.5)
 PASS - "webkitFilter" property for "sepia-box" element at 1s saw something close to: sepia(0.5)
 PASS - "webkitFilter" property for "saturate-box" element at 1s saw something close to: saturate(0.5)
-PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: huerotate(90deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: hue-rotate(90deg)
 PASS - "webkitFilter" property for "invert-box" element at 1s saw something close to: invert(0.5)
 PASS - "webkitFilter" property for "opacity-box" element at 1s saw something close to: opacity(0.5)
 PASS - "webkitFilter" property for "brightness-box" element at 1s saw something close to: brightness(0.5)
index 0cda304..9949d1b 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 0.5s saw something close to: grayscale(0.25)
 PASS - "webkitFilter" property for "sepia-box" element at 0.5s saw something close to: sepia(0.25)
 PASS - "webkitFilter" property for "saturate-box" element at 0.5s saw something close to: saturate(0.75)
-PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: huerotate(45deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: hue-rotate(45deg)
 PASS - "webkitFilter" property for "invert-box" element at 0.5s saw something close to: invert(0.25)
 PASS - "webkitFilter" property for "opacity-box" element at 0.5s saw something close to: opacity(0.75)
 PASS - "webkitFilter" property for "brightness-box" element at 0.5s saw something close to: brightness(0.75)
index 365f074..70a4591 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 1s saw something close to: grayscale(0.5)
 PASS - "webkitFilter" property for "sepia-box" element at 1s saw something close to: sepia(0.5)
 PASS - "webkitFilter" property for "saturate-box" element at 1s saw something close to: saturate(0.5)
-PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: huerotate(90deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: hue-rotate(90deg)
 PASS - "webkitFilter" property for "invert-box" element at 1s saw something close to: invert(0.5)
 PASS - "webkitFilter" property for "opacity-box" element at 1s saw something close to: opacity(0.5)
 PASS - "webkitFilter" property for "brightness-box" element at 1s saw something close to: brightness(0.5)
index 735ecde..6d31b28 100644 (file)
       ["grayscale-anim",  1, "grayscale-box", "webkitFilter", 'grayscale(0.5)', 0.05],
       ["sepia-anim",  1, "sepia-box", "webkitFilter", 'sepia(0.5)', 0.05],
       ["saturate-anim",  1, "saturate-box", "webkitFilter", 'saturate(0.5)', 0.05],
-      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'huerotate(90deg)', 5],
+      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'hue-rotate(90deg)', 5],
       ["invert-anim",  1, "invert-box", "webkitFilter", 'invert(0.5)', 0.05],
       ["opacity-anim",  1, "opacity-box", "webkitFilter", 'opacity(0.5)', 0.05],
       ["brightness-anim",  1, "brightness-box", "webkitFilter", 'brightness(0.5)', 0.05],
index 0cda304..9949d1b 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 0.5s saw something close to: grayscale(0.25)
 PASS - "webkitFilter" property for "sepia-box" element at 0.5s saw something close to: sepia(0.25)
 PASS - "webkitFilter" property for "saturate-box" element at 0.5s saw something close to: saturate(0.75)
-PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: huerotate(45deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: hue-rotate(45deg)
 PASS - "webkitFilter" property for "invert-box" element at 0.5s saw something close to: invert(0.25)
 PASS - "webkitFilter" property for "opacity-box" element at 0.5s saw something close to: opacity(0.75)
 PASS - "webkitFilter" property for "brightness-box" element at 0.5s saw something close to: brightness(0.75)
index 0cda304..9949d1b 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 0.5s saw something close to: grayscale(0.25)
 PASS - "webkitFilter" property for "sepia-box" element at 0.5s saw something close to: sepia(0.25)
 PASS - "webkitFilter" property for "saturate-box" element at 0.5s saw something close to: saturate(0.75)
-PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: huerotate(45deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 0.5s saw something close to: hue-rotate(45deg)
 PASS - "webkitFilter" property for "invert-box" element at 0.5s saw something close to: invert(0.25)
 PASS - "webkitFilter" property for "opacity-box" element at 0.5s saw something close to: opacity(0.75)
 PASS - "webkitFilter" property for "brightness-box" element at 0.5s saw something close to: brightness(0.75)
index e371af2..fccb343 100644 (file)
       ["grayscale-anim",  0.5, "grayscale-box", "webkitFilter", 'grayscale(0.25)', 0.05],
       ["sepia-anim",  0.5, "sepia-box", "webkitFilter", 'sepia(0.25)', 0.05],
       ["saturate-anim",  0.5, "saturate-box", "webkitFilter", 'saturate(0.75)', 0.05],
-      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'huerotate(45deg)', 3],
+      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'hue-rotate(45deg)', 3],
       ["invert-anim",  0.5, "invert-box", "webkitFilter", 'invert(0.25)', 0.05],
       ["opacity-anim",  0.5, "opacity-box", "webkitFilter", 'opacity(0.75)', 0.05],
       ["brightness-anim",  0.5, "brightness-box", "webkitFilter", 'brightness(0.75)', 0.05],
index 26aebb3..2189b30 100644 (file)
       ["grayscale-anim",  0.5, "grayscale-box", "webkitFilter", 'grayscale(0.25)', 0.05],
       ["sepia-anim",  0.5, "sepia-box", "webkitFilter", 'sepia(0.25)', 0.05],
       ["saturate-anim",  0.5, "saturate-box", "webkitFilter", 'saturate(0.75)', 0.05],
-      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'huerotate(45deg)', 5],
+      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'hue-rotate(45deg)', 5],
       ["invert-anim",  0.5, "invert-box", "webkitFilter", 'invert(0.25)', 0.05],
       ["opacity-anim",  0.5, "opacity-box", "webkitFilter", 'opacity(0.75)', 0.05],
       ["brightness-anim",  0.5, "brightness-box", "webkitFilter", 'brightness(0.75)', 0.05],
index af91735..e6006ca 100644 (file)
       ["grayscale-anim",  0.5, "grayscale-box", "webkitFilter", 'grayscale(0.25)', 0.05],
       ["sepia-anim",  0.5, "sepia-box", "webkitFilter", 'sepia(0.25)', 0.05],
       ["saturate-anim",  0.5, "saturate-box", "webkitFilter", 'saturate(0.75)', 0.05],
-      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'huerotate(45deg)', 3],
+      ["huerotate-anim",  0.5, "huerotate-box", "webkitFilter", 'hue-rotate(45deg)', 3],
       ["invert-anim",  0.5, "invert-box", "webkitFilter", 'invert(0.25)', 0.05],
       ["opacity-anim",  0.5, "opacity-box", "webkitFilter", 'opacity(0.75)', 0.05],
       ["brightness-anim",  0.5, "brightness-box", "webkitFilter", 'brightness(0.75)', 0.05],
index 365f074..70a4591 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 1s saw something close to: grayscale(0.5)
 PASS - "webkitFilter" property for "sepia-box" element at 1s saw something close to: sepia(0.5)
 PASS - "webkitFilter" property for "saturate-box" element at 1s saw something close to: saturate(0.5)
-PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: huerotate(90deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: hue-rotate(90deg)
 PASS - "webkitFilter" property for "invert-box" element at 1s saw something close to: invert(0.5)
 PASS - "webkitFilter" property for "opacity-box" element at 1s saw something close to: opacity(0.5)
 PASS - "webkitFilter" property for "brightness-box" element at 1s saw something close to: brightness(0.5)
index 10df20f..11a28d1 100644 (file)
       ["grayscale-anim",  1, "grayscale-box", "webkitFilter", 'grayscale(0.5)', 0.05],
       ["sepia-anim",  1, "sepia-box", "webkitFilter", 'sepia(0.5)', 0.05],
       ["saturate-anim",  1, "saturate-box", "webkitFilter", 'saturate(0.5)', 0.05],
-      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'huerotate(90deg)', 2],
+      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'hue-rotate(90deg)', 2],
       ["invert-anim",  1, "invert-box", "webkitFilter", 'invert(0.5)', 0.05],
       ["opacity-anim",  1, "opacity-box", "webkitFilter", 'opacity(0.5)', 0.05],
       ["brightness-anim",  1, "brightness-box", "webkitFilter", 'brightness(0.5)', 0.05],
index 365f074..70a4591 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 1s saw something close to: grayscale(0.5)
 PASS - "webkitFilter" property for "sepia-box" element at 1s saw something close to: sepia(0.5)
 PASS - "webkitFilter" property for "saturate-box" element at 1s saw something close to: saturate(0.5)
-PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: huerotate(90deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: hue-rotate(90deg)
 PASS - "webkitFilter" property for "invert-box" element at 1s saw something close to: invert(0.5)
 PASS - "webkitFilter" property for "opacity-box" element at 1s saw something close to: opacity(0.5)
 PASS - "webkitFilter" property for "brightness-box" element at 1s saw something close to: brightness(0.5)
index 365f074..70a4591 100644 (file)
@@ -2,7 +2,7 @@
 PASS - "webkitFilter" property for "grayscale-box" element at 1s saw something close to: grayscale(0.5)
 PASS - "webkitFilter" property for "sepia-box" element at 1s saw something close to: sepia(0.5)
 PASS - "webkitFilter" property for "saturate-box" element at 1s saw something close to: saturate(0.5)
-PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: huerotate(90deg)
+PASS - "webkitFilter" property for "huerotate-box" element at 1s saw something close to: hue-rotate(90deg)
 PASS - "webkitFilter" property for "invert-box" element at 1s saw something close to: invert(0.5)
 PASS - "webkitFilter" property for "opacity-box" element at 1s saw something close to: opacity(0.5)
 PASS - "webkitFilter" property for "brightness-box" element at 1s saw something close to: brightness(0.5)
index f35903f..5436020 100644 (file)
       ["grayscale-anim",  1, "grayscale-box", "webkitFilter", 'grayscale(0.5)', 0.05],
       ["sepia-anim",  1, "sepia-box", "webkitFilter", 'sepia(0.5)', 0.05],
       ["saturate-anim",  1, "saturate-box", "webkitFilter", 'saturate(0.5)', 0.05],
-      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'huerotate(90deg)', 2],
+      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'hue-rotate(90deg)', 2],
       ["invert-anim",  1, "invert-box", "webkitFilter", 'invert(0.5)', 0.05],
       ["opacity-anim",  1, "opacity-box", "webkitFilter", 'opacity(0.5)', 0.05],
       ["brightness-anim",  1, "brightness-box", "webkitFilter", 'brightness(0.5)', 0.05],
index f35903f..5436020 100644 (file)
       ["grayscale-anim",  1, "grayscale-box", "webkitFilter", 'grayscale(0.5)', 0.05],
       ["sepia-anim",  1, "sepia-box", "webkitFilter", 'sepia(0.5)', 0.05],
       ["saturate-anim",  1, "saturate-box", "webkitFilter", 'saturate(0.5)', 0.05],
-      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'huerotate(90deg)', 2],
+      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'hue-rotate(90deg)', 2],
       ["invert-anim",  1, "invert-box", "webkitFilter", 'invert(0.5)', 0.05],
       ["opacity-anim",  1, "opacity-box", "webkitFilter", 'opacity(0.5)', 0.05],
       ["brightness-anim",  1, "brightness-box", "webkitFilter", 'brightness(0.5)', 0.05],
index db50404..6431d7d 100644 (file)
       ["grayscale-anim",  1, "grayscale-box", "webkitFilter", 'grayscale(0.5)', 0.05],
       ["sepia-anim",  1, "sepia-box", "webkitFilter", 'sepia(0.5)', 0.05],
       ["saturate-anim",  1, "saturate-box", "webkitFilter", 'saturate(0.5)', 0.05],
-      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'huerotate(90deg)', 2],
+      ["huerotate-anim",  1, "huerotate-box", "webkitFilter", 'hue-rotate(90deg)', 2],
       ["invert-anim",  1, "invert-box", "webkitFilter", 'invert(0.5)', 0.05],
       ["opacity-anim",  1, "opacity-box", "webkitFilter", 'opacity(0.5)', 0.05],
       ["brightness-anim",  1, "brightness-box", "webkitFilter", 'brightness(0.5)', 0.05],
diff --git a/LayoutTests/fast/filter-image/filter-image-animation-expected.txt b/LayoutTests/fast/filter-image/filter-image-animation-expected.txt
new file mode 100644 (file)
index 0000000..1e99d10
--- /dev/null
@@ -0,0 +1,12 @@
+         
+PASS - "backgroundImage" property for "brightness" element at 1s saw something close to: -webkit-filter(url(image.svg), brightness(0.5))
+PASS - "backgroundImage" property for "blur" element at 1s saw something close to: -webkit-filter(url(image.svg), blur(5px))
+PASS - "backgroundImage" property for "grayscale" element at 1s saw something close to: -webkit-filter(url(image.svg), grayscale(0.5))
+PASS - "backgroundImage" property for "sepia" element at 1s saw something close to: -webkit-filter(url(image.svg), sepia(0.5))
+PASS - "backgroundImage" property for "no" element at 1s saw something close to: -webkit-filter(url(blue.svg), sepia(0))
+PASS - "backgroundImage" property for "multiple1" element at 1s saw something close to: -webkit-filter(url(image.svg), sepia(0.5) blur(1.5px) hue-rotate(22.5deg))
+PASS - "backgroundImage" property for "multiple2" element at 1s saw something close to: -webkit-filter(url(image.svg), contrast(0.5) blur(1.5px) hue-rotate(22.5deg))
+PASS - "backgroundImage" property for "generated1" element at 1s saw something close to: -webkit-filter(-webkit-filter(url(image.svg), blur(3px)), sepia(0))
+PASS - "backgroundImage" property for "generated2" element at 1s saw something close to: -webkit-filter(-webkit-cross-fade(url(image.svg), url(image.svg), 50%), sepia(0))
+PASS - "backgroundImage" property for "urlfilter" element at 1s saw something close to: -webkit-filter(url(image.svg), sepia(0.5))
+
diff --git a/LayoutTests/fast/filter-image/filter-image-animation.html b/LayoutTests/fast/filter-image/filter-image-animation.html
new file mode 100644 (file)
index 0000000..612bc8b
--- /dev/null
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <style>
+    .box {
+        height: 100px;
+        width: 100px;
+        margin: 10px;
+        background-color: blue;
+        display: inline-block;
+    }
+
+    #brightness {
+      -webkit-animation: brightness-anim 2s linear;
+    }
+
+    #blur {
+      -webkit-animation: blur-anim 2s linear;
+    }
+
+    #grayscale {
+      -webkit-animation: grayscale-anim 2s linear;
+    }
+
+    #sepia {
+      -webkit-animation: sepia-anim 2s linear;
+    }
+
+    #no {
+      -webkit-animation: no-anim 2s linear;
+    }
+
+    #multiple1 {
+      -webkit-animation: multiple-anim1 2s linear;
+    }
+
+    #multiple2 {
+      -webkit-animation: multiple-anim2 2s linear;
+    }
+
+    #generated1 {
+      -webkit-animation: generated-anim1 2s linear;
+    }
+
+    #generated2 {
+      -webkit-animation: generated-anim2 2s linear;
+    }
+
+    #urlfilter {
+      -webkit-animation: url-to-filter-anim 2s linear;
+    }
+
+
+    @-webkit-keyframes brightness-anim {
+        from { background-image: -webkit-filter(url(resources/image.svg), brightness(1)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), brightness(0)); }
+    }
+
+    @-webkit-keyframes blur-anim {
+        from { background-image: -webkit-filter(url(resources/image.svg), blur(0)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), blur(10px)); }
+    }
+
+    @-webkit-keyframes grayscale-anim {
+        from { background-image: -webkit-filter(url(resources/image.svg), grayscale(1)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), grayscale(0)); }
+    }
+
+    @-webkit-keyframes sepia-anim {
+        from { background-image: -webkit-filter(url(resources/image.svg), sepia(1)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), sepia(0)); }
+    }
+
+    @-webkit-keyframes no-anim {
+        from { background-image: -webkit-filter(url(resources/image.svg), sepia(1)); }
+        to   { background-image: -webkit-filter(url(resources/blue.svg), sepia(0)); }
+    }
+
+    @-webkit-keyframes multiple-anim1 {
+        from { background-image: -webkit-filter(url(resources/image.svg), sepia(0.25) blur(3px) hue-rotate(45deg)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), sepia(0.75)); }
+    }
+
+    @-webkit-keyframes multiple-anim2 {
+        from { background-image: -webkit-filter(url(resources/image.svg), contrast(0.25)); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), contrast(0.75) blur(3px) hue-rotate(45deg)); }
+    }
+
+    @-webkit-keyframes generated-anim1 {
+        from { background-image: -webkit-filter(-webkit-filter(url(resources/image.svg), blur(3px)), sepia(1)); }
+        to   { background-image: -webkit-filter(-webkit-filter(url(resources/image.svg), blur(3px)), sepia(0)); }
+    }
+
+    @-webkit-keyframes generated-anim2 {
+        from { background-image: -webkit-filter(-webkit-cross-fade(url(resources/image.svg), url(resources/image.svg), 50%), sepia(1)); }
+        to   { background-image: -webkit-filter(-webkit-cross-fade(url(resources/image.svg), url(resources/image.svg), 50%), sepia(0)); }
+    }
+
+    @-webkit-keyframes url-to-filter-anim {
+        from { background-image: url(resources/image.svg); }
+        to   { background-image: -webkit-filter(url(resources/image.svg), sepia(0.5)); }
+    }
+  </style>
+  <script src="../../animations/resources/animation-test-helpers.js"></script>
+  <script type="text/javascript">
+    const expectedValues = [
+      // [animation-name, time, element-id, property, expected-value, tolerance]
+      ["brightness-anim",  1, "brightness", "backgroundImage", "-webkit-filter(url(image.svg), brightness(0.5))", 0.05],
+      ["blur-anim",  1, "blur", "backgroundImage", "-webkit-filter(url(image.svg), blur(5px))", 0.05],
+      ["grayscale-anim",  1, "grayscale", "backgroundImage", "-webkit-filter(url(image.svg), grayscale(0.5))", 0.05],
+      ["sepia-anim",  1, "sepia", "backgroundImage", "-webkit-filter(url(image.svg), sepia(0.5))", 0.05],
+      ["no-anim",  1, "no", "backgroundImage", "-webkit-filter(url(blue.svg), sepia(0))", 0],
+      ["multiple-anim1",  1, "multiple1", "backgroundImage", "-webkit-filter(url(image.svg), sepia(0.5) blur(1.5px) hue-rotate(22.5deg))", 0.05],
+      ["multiple-anim2",  1, "multiple2", "backgroundImage", "-webkit-filter(url(image.svg), contrast(0.5) blur(1.5px) hue-rotate(22.5deg))", 0.05],
+      // FIXME: We need to support generated images as input to other generated images for animations first.
+      ["generated-anim1",  1, "generated1", "backgroundImage", "-webkit-filter(-webkit-filter(url(image.svg), blur(3px)), sepia(0))", 0],
+      ["generated-anim2",  1, "generated2", "backgroundImage", "-webkit-filter(-webkit-cross-fade(url(image.svg), url(image.svg), 50%), sepia(0))", 0],
+      ["url-to-filter-anim",  1, "urlfilter", "backgroundImage", " -webkit-filter(url(image.svg), sepia(0.5))", 0],
+    ];
+    
+    runAnimationTest(expectedValues);
+  </script>
+</head>
+<body>
+
+<div class="box" id="brightness"></div>
+<div class="box" id="blur"></div>
+<div class="box" id="grayscale"></div>
+<div class="box" id="sepia"></div>
+<div class="box" id="no"></div>
+<div class="box" id="multiple1"></div>
+<div class="box" id="multiple2"></div>
+<div class="box" id="generated1"></div>
+<div class="box" id="generated2"></div>
+<div class="box" id="urlfilter"></div>
+
+<div id="result">
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/filter-image/resources/blue.svg b/LayoutTests/fast/filter-image/resources/blue.svg
new file mode 100644 (file)
index 0000000..111dcae
--- /dev/null
@@ -0,0 +1,3 @@
+<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100'>
+       <rect width='100' height='100' fill='blue'/>
+</svg>
\ No newline at end of file
index 216e053..b60e2b1 100644 (file)
@@ -3,16 +3,17 @@ layer at (0,0) size 800x600
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x54
-        RenderText {#text} at (0,0) size 577x18
-          text run at (0,0) width 577: "PASS - \"backgroundImage\" property for \"box\" element at 2.5s saw something close to: 0.5"
-        RenderBR {BR} at (577,14) size 0x0
-        RenderText {#text} at (0,18) size 704x18
-          text run at (0,18) width 704: "PASS - \"backgroundImage\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (704,32) size 0x0
-        RenderText {#text} at (0,36) size 769x18
-          text run at (0,36) width 769: "PASS - \"backgroundImage\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (769,50) size 0x0
+      RenderBlock {DIV} at (0,0) size 784x72
+        RenderText {#text} at (0,0) size 733x36
+          text run at (0,0) width 733: "PASS - \"backgroundImage\" property for \"box\" element at 2.5s saw something close to: -webkit-cross-fade(url(blue-"
+          text run at (0,18) width 226: "100.png), url(green-100.png), 50%)"
+        RenderBR {BR} at (226,32) size 0x0
+        RenderText {#text} at (0,36) size 704x18
+          text run at (0,36) width 704: "PASS - \"backgroundImage\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (704,50) size 0x0
+        RenderText {#text} at (0,54) size 769x18
+          text run at (0,54) width 769: "PASS - \"backgroundImage\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (769,68) size 0x0
 layer at (100,100) size 100x100
   RenderBlock (positioned) {DIV} at (100,100) size 100x100 [bgcolor=#FF0000]
 layer at (100,300) size 100x100
index bf3d6e1..c0ebbc6 100644 (file)
@@ -3,16 +3,17 @@ layer at (0,0) size 800x600
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x54
-        RenderText {#text} at (0,0) size 587x18
-          text run at (0,0) width 587: "PASS - \"borderImageSource\" property for \"box\" element at 2.5s saw something close to: 0.5"
-        RenderBR {BR} at (587,14) size 0x0
-        RenderText {#text} at (0,18) size 714x18
-          text run at (0,18) width 714: "PASS - \"borderImageSource\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (714,32) size 0x0
-        RenderText {#text} at (0,36) size 779x18
-          text run at (0,36) width 779: "PASS - \"borderImageSource\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (779,50) size 0x0
+      RenderBlock {DIV} at (0,0) size 784x72
+        RenderText {#text} at (0,0) size 756x36
+          text run at (0,0) width 756: "PASS - \"borderImageSource\" property for \"box\" element at 2.5s saw something close to: -webkit-cross-fade(url(stripes-"
+          text run at (0,18) width 217: "100.png), url(green-100.png), 0.5)"
+        RenderBR {BR} at (217,32) size 0x0
+        RenderText {#text} at (0,36) size 714x18
+          text run at (0,36) width 714: "PASS - \"borderImageSource\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (714,50) size 0x0
+        RenderText {#text} at (0,54) size 779x18
+          text run at (0,54) width 779: "PASS - \"borderImageSource\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (779,68) size 0x0
 layer at (100,100) size 106x106
   RenderBlock (positioned) {DIV} at (100,100) size 106x106 [bgcolor=#008000] [border: (3px none #000000)]
 layer at (100,250) size 106x106
index fd66054..a4c7c5a 100644 (file)
@@ -3,16 +3,17 @@ layer at (0,0) size 800x600
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x54
-        RenderText {#text} at (0,0) size 552x18
-          text run at (0,0) width 552: "PASS - \"listStyleImage\" property for \"box\" element at 2.5s saw something close to: 0.5"
-        RenderBR {BR} at (552,14) size 0x0
-        RenderText {#text} at (0,18) size 679x18
-          text run at (0,18) width 679: "PASS - \"listStyleImage\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (679,32) size 0x0
-        RenderText {#text} at (0,36) size 744x18
-          text run at (0,36) width 744: "PASS - \"listStyleImage\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
-        RenderBR {BR} at (744,50) size 0x0
+      RenderBlock {DIV} at (0,0) size 784x72
+        RenderText {#text} at (0,0) size 769x36
+          text run at (0,0) width 769: "PASS - \"listStyleImage\" property for \"box\" element at 2.5s saw something close to: -webkit-cross-fade(url(blue-100.png),"
+          text run at (0,18) width 161: "url(green-100.png), 50%)"
+        RenderBR {BR} at (161,32) size 0x0
+        RenderText {#text} at (0,36) size 679x18
+          text run at (0,36) width 679: "PASS - \"listStyleImage\" property for \"box\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (679,50) size 0x0
+        RenderText {#text} at (0,54) size 744x18
+          text run at (0,54) width 744: "PASS - \"listStyleImage\" property for \"boxShorthand\" and \"boxStatic\" elements at 2.5s are close enough to each other"
+        RenderBR {BR} at (744,68) size 0x0
 layer at (100,116) size 140x100
   RenderBlock (positioned) {UL} at (100,116) size 140x100
     RenderListItem {LI} at (40,0) size 100x104
index 587a619..5244678 100644 (file)
@@ -3,13 +3,14 @@ layer at (0,0) size 800x600
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x36
-        RenderText {#text} at (0,0) size 623x18
-          text run at (0,0) width 623: "PASS - \"webkitMaskBoxImage\" property for \"box\" element at 2.25s saw something close to: 0.25"
-        RenderBR {BR} at (623,14) size 0x0
-        RenderText {#text} at (0,18) size 742x18
-          text run at (0,18) width 742: "PASS - \"webkitMaskBoxImage\" property for \"box\" and \"boxStatic\" elements at 2.25s are close enough to each other"
-        RenderBR {BR} at (742,32) size 0x0
+      RenderBlock {DIV} at (0,0) size 784x54
+        RenderText {#text} at (0,0) size 784x36
+          text run at (0,0) width 784: "PASS - \"webkitMaskBoxImage\" property for \"box\" element at 2.25s saw something close to: -webkit-cross-fade(url(stripes-"
+          text run at (0,18) width 226: "100.png), url(green-100.png), 25%)"
+        RenderBR {BR} at (226,32) size 0x0
+        RenderText {#text} at (0,36) size 742x18
+          text run at (0,36) width 742: "PASS - \"webkitMaskBoxImage\" property for \"box\" and \"boxStatic\" elements at 2.25s are close enough to each other"
+        RenderBR {BR} at (742,50) size 0x0
 layer at (100,100) size 200x200
   RenderImage {IMG} at (100,100) size 200x200 [bgcolor=#FF0000]
 layer at (100,300) size 200x200
index 91ed8ce..e610248 100644 (file)
@@ -3,13 +3,14 @@ layer at (0,0) size 800x600
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x36
-        RenderText {#text} at (0,0) size 596x18
-          text run at (0,0) width 596: "PASS - \"webkitMaskImage\" property for \"box\" element at 2.25s saw something close to: 0.25"
-        RenderBR {BR} at (596,14) size 0x0
-        RenderText {#text} at (0,18) size 715x18
-          text run at (0,18) width 715: "PASS - \"webkitMaskImage\" property for \"box\" and \"boxStatic\" elements at 2.25s are close enough to each other"
-        RenderBR {BR} at (715,32) size 0x0
+      RenderBlock {DIV} at (0,0) size 784x54
+        RenderText {#text} at (0,0) size 757x36
+          text run at (0,0) width 757: "PASS - \"webkitMaskImage\" property for \"box\" element at 2.25s saw something close to: -webkit-cross-fade(url(stripes-"
+          text run at (0,18) width 226: "100.png), url(green-100.png), 25%)"
+        RenderBR {BR} at (226,32) size 0x0
+        RenderText {#text} at (0,36) size 715x18
+          text run at (0,36) width 715: "PASS - \"webkitMaskImage\" property for \"box\" and \"boxStatic\" elements at 2.25s are close enough to each other"
+        RenderBR {BR} at (715,50) size 0x0
 layer at (100,100) size 100x100
   RenderImage {IMG} at (100,100) size 100x100 [bgcolor=#FF0000]
 layer at (100,200) size 100x100
index 2fa3394..06e8d4e 100644 (file)
@@ -1,3 +1,47 @@
+2013-08-30  Dirk Schulze  <krit@webkit.org>
+
+        Animate CSS Image filter() function
+        https://bugs.webkit.org/show_bug.cgi?id=119938
+
+        Reviewed by Simon Fraser.
+
+        With this patch, the new introduced CSS Image function filter() can be
+        animated. According to the spec, just filter functions can be
+        interpolated.
+
+        The patch also prepares StyleImage blending for interpolation of other
+        generated images like gradients or cross-fade().
+
+        http://dev.w3.org/fxtf/filters/#interpolating-filter-image
+
+        Test: fast/filter-image/filter-image-animation.html
+
+        * css/CSSComputedStyleDeclaration.cpp: Reuse the code that creates a
+            CSSValueList from ComputeStyle logic.
+        (WebCore::valueForPixel):
+            For StyleRules we want to have not-adjusted length values.
+        (WebCore::ComputedStyleExtractor::valueForShadow):
+            Add argument to switch between adjusted and not-adjusted length.
+        (WebCore::ComputedStyleExtractor::valueForFilter):
+            Ditto.
+        (WebCore::ComputedStyleExtractor::propertyValue):
+        * css/CSSComputedStyleDeclaration.h:
+        * css/CSSFilterImageValue.h: Add helper functions
+            for animating filters. We need to pass the FilterOperations for
+            the image generation and the CSSValueList for StyleRule.
+        (WebCore::CSSFilterImageValue::filterOperations):
+        (WebCore::CSSFilterImageValue::setFilterOperations):
+        (WebCore::CSSFilterImageValue::cachedImage):
+        * page/animation/CSSPropertyAnimation.cpp:
+            Add animation code to support animations between two filter()
+            function values.
+        (WebCore::blendFilterOperations):
+        (WebCore::blendFunc):
+        (WebCore::filterBlend):
+        * rendering/style/StyleGeneratedImage.h: Add helper functions.
+        (WebCore::CSSFilterImageValue::imageValue):
+
+
 2013-08-30  Leo Yang  <leoyang@blackberry.com>
 
         Make sure remove CachedResourceClient when destructing IconLoader
index a11d6c5..e25e0ff 100644 (file)
@@ -900,21 +900,49 @@ static PassRefPtr<CSSValue> valueForCustomFilterParameter(const RenderObject* re
 }
 #endif // ENABLE(CSS_SHADERS)
 
+static inline PassRefPtr<CSSPrimitiveValue> adjustLengthForZoom(double length, const RenderStyle* style, AdjustPixelValuesForComputedStyle adjust)
+{
+    return adjust == AdjustPixelValues ? zoomAdjustedPixelValue(length, style) : cssValuePool().createValue(length, CSSPrimitiveValue::CSS_PX);
+}
+
+static inline PassRefPtr<CSSPrimitiveValue> adjustLengthForZoom(const Length& length, const RenderStyle* style, AdjustPixelValuesForComputedStyle adjust)
+{
+    return adjust == AdjustPixelValues ? zoomAdjustedPixelValue(length.value(), style) : cssValuePool().createValue(length);
+}
+
+PassRefPtr<CSSValue> ComputedStyleExtractor::valueForShadow(const ShadowData* shadow, CSSPropertyID propertyID, const RenderStyle* style, AdjustPixelValuesForComputedStyle adjust)
+{
+    if (!shadow)
+        return cssValuePool().createIdentifierValue(CSSValueNone);
+
+    RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
+    for (const ShadowData* currShadowData = shadow; currShadowData; currShadowData = currShadowData->next()) {
+        RefPtr<CSSPrimitiveValue> x = adjustLengthForZoom(currShadowData->x(), style, adjust);
+        RefPtr<CSSPrimitiveValue> y = adjustLengthForZoom(currShadowData->y(), style, adjust);
+        RefPtr<CSSPrimitiveValue> blur = adjustLengthForZoom(currShadowData->radius(), style, adjust);
+        RefPtr<CSSPrimitiveValue> spread = propertyID == CSSPropertyTextShadow ? PassRefPtr<CSSPrimitiveValue>() : adjustLengthForZoom(currShadowData->spread(), style, adjust);
+        RefPtr<CSSPrimitiveValue> style = propertyID == CSSPropertyTextShadow || currShadowData->style() == Normal ? PassRefPtr<CSSPrimitiveValue>() : cssValuePool().createIdentifierValue(CSSValueInset);
+        RefPtr<CSSPrimitiveValue> color = cssValuePool().createColorValue(currShadowData->color().rgb());
+        list->prepend(ShadowValue::create(x.release(), y.release(), blur.release(), spread.release(), style.release(), color.release()));
+    }
+    return list.release();
+}
+
 #if ENABLE(CSS_FILTERS)
-PassRefPtr<CSSValue> ComputedStyleExtractor::valueForFilter(const RenderObject* renderer, const RenderStyle* style) const
+PassRefPtr<CSSValue> ComputedStyleExtractor::valueForFilter(const RenderObject* renderer, const RenderStyle* style, const FilterOperations& filterOperations, AdjustPixelValuesForComputedStyle adjust)
 {
 #if !ENABLE(CSS_SHADERS)
     UNUSED_PARAM(renderer);
 #endif
-    if (style->filter().operations().isEmpty())
+    if (filterOperations.operations().isEmpty())
         return cssValuePool().createIdentifierValue(CSSValueNone);
 
     RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
 
     RefPtr<WebKitCSSFilterValue> filterValue;
 
-    Vector<RefPtr<FilterOperation> >::const_iterator end = style->filter().operations().end();
-    for (Vector<RefPtr<FilterOperation> >::const_iterator it = style->filter().operations().begin(); it != end; ++it) {
+    Vector<RefPtr<FilterOperation> >::const_iterator end = filterOperations.operations().end();
+    for (Vector<RefPtr<FilterOperation> >::const_iterator it = filterOperations.operations().begin(); it != end; ++it) {
         FilterOperation* filterOperation = (*it).get();
         switch (filterOperation->getOperationType()) {
         case FilterOperation::REFERENCE: {
@@ -974,7 +1002,7 @@ PassRefPtr<CSSValue> ComputedStyleExtractor::valueForFilter(const RenderObject*
         case FilterOperation::BLUR: {
             BlurFilterOperation* blurOperation = static_cast<BlurFilterOperation*>(filterOperation);
             filterValue = WebKitCSSFilterValue::create(WebKitCSSFilterValue::BlurFilterOperation);
-            filterValue->append(zoomAdjustedPixelValue(blurOperation->stdDeviation().value(), style));
+            filterValue->append(adjustLengthForZoom(blurOperation->stdDeviation(), style, adjust));
             break;
         }
         case FilterOperation::DROP_SHADOW: {
@@ -982,7 +1010,7 @@ PassRefPtr<CSSValue> ComputedStyleExtractor::valueForFilter(const RenderObject*
             filterValue = WebKitCSSFilterValue::create(WebKitCSSFilterValue::DropShadowFilterOperation);
             // We want our computed style to look like that of a text shadow (has neither spread nor inset style).
             ShadowData shadowData = ShadowData(dropShadowOperation->location(), dropShadowOperation->stdDeviation(), 0, Normal, false, dropShadowOperation->color());
-            filterValue->append(valueForShadow(&shadowData, CSSPropertyTextShadow, style));
+            filterValue->append(valueForShadow(&shadowData, CSSPropertyTextShadow, style, adjust));
             break;
         }
 #if ENABLE(CSS_SHADERS)
@@ -1328,23 +1356,6 @@ bool ComputedStyleExtractor::useFixedFontDefaultSize() const
     return style->fontDescription().useFixedDefaultSize();
 }
 
-PassRefPtr<CSSValue> ComputedStyleExtractor::valueForShadow(const ShadowData* shadow, CSSPropertyID propertyID, const RenderStyle* style) const
-{
-    if (!shadow)
-        return cssValuePool().createIdentifierValue(CSSValueNone);
-
-    RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
-    for (const ShadowData* s = shadow; s; s = s->next()) {
-        RefPtr<CSSPrimitiveValue> x = zoomAdjustedPixelValue(s->x(), style);
-        RefPtr<CSSPrimitiveValue> y = zoomAdjustedPixelValue(s->y(), style);
-        RefPtr<CSSPrimitiveValue> blur = zoomAdjustedPixelValue(s->radius(), style);
-        RefPtr<CSSPrimitiveValue> spread = propertyID == CSSPropertyTextShadow ? PassRefPtr<CSSPrimitiveValue>() : zoomAdjustedPixelValue(s->spread(), style);
-        RefPtr<CSSPrimitiveValue> style = propertyID == CSSPropertyTextShadow || s->style() == Normal ? PassRefPtr<CSSPrimitiveValue>() : cssValuePool().createIdentifierValue(CSSValueInset);
-        RefPtr<CSSPrimitiveValue> color = cssValuePool().createColorValue(s->color().rgb());
-        list->prepend(ShadowValue::create(x.release(), y.release(), blur.release(), spread.release(), style.release(), color.release()));
-    }
-    return list.release();
-}
 
 static CSSValueID identifierForFamily(const AtomicString& family)
 {
@@ -2832,7 +2843,7 @@ PassRefPtr<CSSValue> ComputedStyleExtractor::propertyValue(CSSPropertyID propert
 #endif
 #if ENABLE(CSS_FILTERS)
         case CSSPropertyWebkitFilter:
-            return valueForFilter(renderer, style.get());
+            return valueForFilter(renderer, style.get(), style->filter());
 #endif
 #if ENABLE(CSS_COMPOSITING)
         case CSSPropertyWebkitBlendMode:
index 17f04cc..b49b119 100644 (file)
@@ -31,6 +31,7 @@ namespace WebCore {
 class CSSPrimitiveValue;
 class CSSValueList;
 class Color;
+class FilterOperations;
 class MutableStylePropertySet;
 class Node;
 class RenderObject;
@@ -47,6 +48,8 @@ class CustomFilterParameter;
 
 enum EUpdateLayout { DoNotUpdateLayout = false, UpdateLayout = true };
 
+enum AdjustPixelValuesForComputedStyle { AdjustPixelValues, DoNotAdjustPixelValues };
+
 class ComputedStyleExtractor {
 public:
     ComputedStyleExtractor(PassRefPtr<Node>, bool allowVisitedStyle = false, PseudoId = NOPSEUDO);
@@ -60,6 +63,10 @@ public:
     bool useFixedFontDefaultSize() const;
     bool propertyMatches(CSSPropertyID, const CSSValue*) const;
 
+#if ENABLE(CSS_FILTERS)
+    static PassRefPtr<CSSValue> valueForFilter(const RenderObject*, const RenderStyle*, const FilterOperations&, AdjustPixelValuesForComputedStyle = AdjustPixelValues);
+#endif
+
 private:
     // The styled node is either the node passed into computedPropertyValue, or the
     // PseudoElement for :before and :after if they exist.
@@ -72,13 +79,9 @@ private:
     PassRefPtr<SVGPaint> adjustSVGPaintForCurrentColor(PassRefPtr<SVGPaint>, RenderStyle*) const;
 #endif
 
-    PassRefPtr<CSSValue> valueForShadow(const ShadowData*, CSSPropertyID, const RenderStyle*) const;
+    static PassRefPtr<CSSValue> valueForShadow(const ShadowData*, CSSPropertyID, const RenderStyle*, AdjustPixelValuesForComputedStyle = AdjustPixelValues);
     PassRefPtr<CSSPrimitiveValue> currentColorOrValidColor(RenderStyle*, const Color&) const;
 
-#if ENABLE(CSS_FILTERS)
-    PassRefPtr<CSSValue> valueForFilter(const RenderObject*, const RenderStyle*) const;
-#endif
-
     PassRefPtr<CSSValueList> getCSSPropertyValuesForShorthandProperties(const StylePropertyShorthand&) const;
     PassRefPtr<CSSValueList> getCSSPropertyValuesForSidesShorthand(const StylePropertyShorthand&) const;
     PassRefPtr<CSSValueList> getBackgroundShorthandValue() const;
index 908c4a2..d8c23db 100644 (file)
@@ -162,8 +162,12 @@ bool CSSFilterImageValue::hasFailedOrCanceledSubresources() const
 
 bool CSSFilterImageValue::equals(const CSSFilterImageValue& other) const
 {
-    return compareCSSValuePtr(m_imageValue, other.m_imageValue)
-        && compareCSSValuePtr(m_filterValue, other.m_filterValue);
+    return equalInputImages(other) && compareCSSValuePtr(m_filterValue, other.m_filterValue);
+}
+
+bool CSSFilterImageValue::equalInputImages(const CSSFilterImageValue& other) const
+{
+    return compareCSSValuePtr(m_imageValue, other.m_imageValue);
 }
 
 } // namespace WebCore
index 6753045..f2f3092 100644 (file)
@@ -70,8 +70,17 @@ public:
 
     bool equals(const CSSFilterImageValue&) const;
 
+    bool equalInputImages(const CSSFilterImageValue&) const;
+
     void createFilterOperations(StyleResolver*);
 
+    const FilterOperations& filterOperations() const { return m_filterOperations; }
+    void setFilterOperations(const FilterOperations& filterOperations)
+    {
+        m_filterOperations = filterOperations;
+    }
+    CachedImage* cachedImage() const { return m_cachedImage.get(); }
+
 private:
     CSSFilterImageValue(PassRefPtr<CSSValue> imageValue, PassRefPtr<CSSValue> filterValue)
         : CSSImageGeneratorValue(FilterImageClass)
@@ -110,6 +119,12 @@ private:
     FilterSubimageObserverProxy m_filterSubimageObserver;
 };
 
+inline CSSFilterImageValue* toCSSFilterImageValue(CSSImageGeneratorValue* value)
+{
+    ASSERT_WITH_SECURITY_IMPLICATION(!value || value->isFilterImageValue());
+    return static_cast<CSSFilterImageValue*>(value);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(CSS_FILTERS)
index 6b51b5b..7d2a538 100644 (file)
@@ -31,7 +31,9 @@
 #include "CSSPropertyAnimation.h"
 
 #include "AnimationBase.h"
+#include "CSSComputedStyleDeclaration.h"
 #include "CSSCrossfadeValue.h"
+#include "CSSFilterImageValue.h"
 #include "CSSImageGeneratorValue.h"
 #include "CSSImageValue.h"
 #include "CSSPrimitiveValue.h"
@@ -172,30 +174,35 @@ static inline PassRefPtr<FilterOperation> blendFunc(const AnimationBase* anim, F
     return toOp->blend(fromOp, progress, blendToPassthrough);
 }
 
+static inline void blendFilterOperations(const AnimationBase* anim, FilterOperations& result, const FilterOperations& from, const FilterOperations& to, double progress)
+{
+    size_t fromSize = from.operations().size();
+    size_t toSize = to.operations().size();
+    size_t size = max(fromSize, toSize);
+    for (size_t i = 0; i < size; i++) {
+        RefPtr<FilterOperation> fromOp = (i < fromSize) ? from.operations()[i].get() : 0;
+        RefPtr<FilterOperation> toOp = (i < toSize) ? to.operations()[i].get() : 0;
+        RefPtr<FilterOperation> blendedOp = toOp ? blendFunc(anim, fromOp.get(), toOp.get(), progress) : (fromOp ? blendFunc(anim, 0, fromOp.get(), progress, true) : 0);
+        if (blendedOp)
+            result.operations().append(blendedOp);
+        else {
+            RefPtr<FilterOperation> identityOp = PassthroughFilterOperation::create();
+            if (progress > 0.5)
+                result.operations().append(toOp ? toOp : identityOp);
+            else
+                result.operations().append(fromOp ? fromOp : identityOp);
+        }
+    }
+}
+
 static inline FilterOperations blendFunc(const AnimationBase* anim, const FilterOperations& from, const FilterOperations& to, double progress)
 {
     FilterOperations result;
 
     // If we have a filter function list, use that to do a per-function animation.
-    if (anim->filterFunctionListsMatch()) {
-        size_t fromSize = from.operations().size();
-        size_t toSize = to.operations().size();
-        size_t size = max(fromSize, toSize);
-        for (size_t i = 0; i < size; i++) {
-            RefPtr<FilterOperation> fromOp = (i < fromSize) ? from.operations()[i].get() : 0;
-            RefPtr<FilterOperation> toOp = (i < toSize) ? to.operations()[i].get() : 0;
-            RefPtr<FilterOperation> blendedOp = toOp ? blendFunc(anim, fromOp.get(), toOp.get(), progress) : (fromOp ? blendFunc(anim, 0, fromOp.get(), progress, true) : 0);
-            if (blendedOp)
-                result.operations().append(blendedOp);
-            else {
-                RefPtr<FilterOperation> identityOp = PassthroughFilterOperation::create();
-                if (progress > 0.5)
-                    result.operations().append(toOp ? toOp : identityOp);
-                else
-                    result.operations().append(fromOp ? fromOp : identityOp);
-            }
-        }
-    } else {
+    if (anim->filterFunctionListsMatch())
+        blendFilterOperations(anim, result, from, to, progress);
+    else {
         // If the filter function lists don't match, we could try to cross-fade, but don't yet have a way to represent that in CSS.
         // For now we'll just fail to animate.
         result = to;
@@ -203,6 +210,27 @@ static inline FilterOperations blendFunc(const AnimationBase* anim, const Filter
 
     return result;
 }
+
+static inline PassRefPtr<StyleImage> filterBlend(const AnimationBase* anim, StyleImage* from, StyleImage* to, double progress)
+{
+    CSSFilterImageValue* fromValue = static_cast<CSSFilterImageValue*>(from->data());
+    CSSFilterImageValue* toValue = static_cast<CSSFilterImageValue*>(to->data());
+
+    FilterOperations filterOperationsResult;
+    blendFilterOperations(anim, filterOperationsResult, fromValue->filterOperations(), toValue->filterOperations(), progress);
+    if (!toValue->cachedImage())
+        return to;
+
+    RefPtr<StyleCachedImage> styledImage = StyleCachedImage::create(toValue->cachedImage());
+
+    RefPtr<CSSImageValue> imageValue = CSSImageValue::create(toValue->cachedImage()->url(), styledImage.get());
+    RefPtr<CSSValue> filterValue = ComputedStyleExtractor::valueForFilter(anim->renderer(), anim->renderer()->style(),
+        filterOperationsResult, DoNotAdjustPixelValues);
+    RefPtr<CSSFilterImageValue> result = CSSFilterImageValue::create(imageValue, filterValue);
+    result->setFilterOperations(filterOperationsResult);
+
+    return StyleGeneratedImage::create(result.get());
+}
 #endif // ENABLE(CSS_FILTERS)
 
 static inline EVisibility blendFunc(const AnimationBase* anim, EVisibility from, EVisibility to, double progress)
@@ -279,11 +307,36 @@ static inline PassRefPtr<StyleImage> blendFunc(const AnimationBase* anim, StyleI
     if (!from || !to)
         return to;
 
+    // Animation between two generated images. Cross fade for all other cases.
+    if (from->isGeneratedImage() && to->isGeneratedImage()) {
+        CSSImageGeneratorValue* fromGenerated = toStyleGeneratedImage(from)->imageValue();
+        CSSImageGeneratorValue* toGenerated = toStyleGeneratedImage(to)->imageValue();
+
+#if ENABLE(CSS_FILTERS)
+        if (fromGenerated->isFilterImageValue() && toGenerated->isFilterImageValue()) {
+            // Animation of generated images just possible if input images are equal.
+            // Otherwise fall back to cross fade animation.
+            CSSFilterImageValue* fromFitler = toCSSFilterImageValue(fromGenerated);
+            CSSFilterImageValue* toFitler = toCSSFilterImageValue(toGenerated);
+            if (fromFitler->equalInputImages(*toFitler))
+                return filterBlend(anim, from, to, progress);
+        }
+#else
+        UNUSED_PARAM(fromGenerated);
+        UNUSED_PARAM(toGenerated);
+#endif
+        // FIXME: Add support for animation between two cross-fade() functions.
+        // https://bugs.webkit.org/show_bug.cgi?id=119955
+
+        // FIXME: Add support for animation between two *gradient() functions.
+        // https://bugs.webkit.org/show_bug.cgi?id=119956
+}
+
+    // FIXME: Add support cross fade between cached and generated images.
+    // https://bugs.webkit.org/show_bug.cgi?id=78293
     if (from->isCachedImage() && to->isCachedImage())
         return crossfadeBlend(anim, static_cast<StyleCachedImage*>(from), static_cast<StyleCachedImage*>(to), progress);
 
-    // FIXME: Support transitioning generated images as well. (gradients, etc.)
-
     return to;
 }
 
index 57f78dd..0ea4ffb 100644 (file)
@@ -39,6 +39,7 @@ public:
     }
 
     virtual WrappedImagePtr data() const { return m_imageGeneratorValue.get(); }
+    CSSImageGeneratorValue* imageValue() const { return m_imageGeneratorValue.get(); }
 
     virtual PassRefPtr<CSSValue> cssValue() const;
 
@@ -61,5 +62,11 @@ private:
     bool m_fixedSize;
 };
 
+inline StyleGeneratedImage* toStyleGeneratedImage(StyleImage* image)
+{
+    ASSERT_WITH_SECURITY_IMPLICATION(!image || image->isGeneratedImage());
+    return static_cast<StyleGeneratedImage*>(image);
+}
+
 }
 #endif