From cb729fce1dbe726e90b2ab9ab5b335b037a25811 Mon Sep 17 00:00:00 2001 From: meetikasharma <165726045+meetikasharma@users.noreply.github.com> Date: Wed, 15 May 2024 17:21:06 -0700 Subject: [PATCH] Support Svg images as background for WinUI3 renderer (#8911) * Initial commit for supporting svg background images * Fixes for svg images as url and data * Add Rasterize size of svg image on background thread * Call TileControl::LoadImageBrush once svg size is calculated on background thread * Addressed comments * Support for svg xml in the data scheme for background images * Addressed comments * Handle base64 svg data scheme for background image and add sample payload * Updated IsSvgImage check and addressed comments * Clean up code * Updated IsSvgImage check --- ...ptiveCard.BackgroundImage.DataUri.svg.json | 13 ++ ...rd.BackgroundImage.FillMode.Cover.svg.json | 58 +++++++ ...d.BackgroundImage.FillMode.Repeat.svg.json | 57 +++++++ ...Image.FillMode.RepeatHorizontally.svg.json | 58 +++++++ ...ndImage.FillMode.RepeatVertically.svg.json | 58 +++++++ ...tiveCard.BackgroundImage.FillMode.svg.json | 57 +++++++ ...groundImage.FillVerticalAlignment.svg.json | 58 +++++++ .../Elements/Column.BackgroundImage.svg.json | 141 ++++++++++++++++++ .../Container.BackgroundImage.svg.json | 47 ++++++ .../lib/AdaptiveImageRenderer.cpp | 71 +-------- source/uwp/SharedRenderer/lib/TileControl.cpp | 25 ++++ source/uwp/SharedRenderer/lib/TileControl.h | 2 + source/uwp/SharedRenderer/lib/Util.cpp | 68 +++++++++ source/uwp/SharedRenderer/lib/Util.h | 15 ++ source/uwp/SharedRenderer/lib/XamlBuilder.h | 6 - source/uwp/SharedRenderer/lib/XamlHelpers.cpp | 126 +++++++++++++--- source/uwp/SharedRenderer/lib/XamlHelpers.h | 4 + 17 files changed, 771 insertions(+), 93 deletions(-) create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.DataUri.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Cover.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Repeat.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatHorizontally.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatVertically.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.svg.json create mode 100644 samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillVerticalAlignment.svg.json create mode 100644 samples/v1.6/Elements/Column.BackgroundImage.svg.json create mode 100644 samples/v1.6/Elements/Container.BackgroundImage.svg.json diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.DataUri.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.DataUri.svg.json new file mode 100644 index 000000000..1d5fe8f88 --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.DataUri.svg.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiICBoZWlnaHQ9IjI5NC40IiB2aWV3Qm94PSIwIDAgNzAwLjAwMDAxIDI5NC40MjI1OCI+IAo8c3R5bGU+CiAgCiAgLnRlc3QgewogICAgYW5pbWF0aW9uOiBmYWRlIDNzIGluZmluaXRlIGFsdGVybmF0ZTsgCiAgICAKICB9CiAgCiAgQGtleWZyYW1lcyBmYWRlIHsKICAgIAogICAgZnJvbSB7CiAgICAgIG9wYWNpdHk6IDAKICAgIH0KICAgIAogICAgdG8gewogICAgICAKICAgICAgb3BhY2l0eToxCiAgICB9CiAgICAKICB9CiAgCiAgPC9zdHlsZT4KICAKICA8cGF0aCBjbGFzcz0idGVzdCIgZD0iTTYwMi4yIDEyOC42Yy0xMS43IDIuNy00Mi42IDQuMi00Mi42IDQuMmwtMy44IDEyLjJzMTUuMy0xLjMgMjYuNS0uMmMwIDAgMy42LS4zIDQgNCAuMiA0LjQtLjMgOS0uMyA5cy0uMyAyLjctNCAzLjRjLTQuMy43LTMzLjMgMi0zMy4zIDJMNTQ0IDE3OXMtMS43IDMuNiAyLjIgMi42YzMuNi0xIDM0LTYuOCAzOC02IDQuMiAxIDkgNi44IDcuNiAxMi0xLjYgNi4zLTMyIDI1LjctNTAuNCAyNC40IDAgMC05LjcuNi0xNy44LTEyLjUtNy44LTEyLjUgMi43LTM2IDIuNy0zNnMtNC43LTExLTEuMi0xNWMwIDAgMi0xLjcgOC0yLjJsNy41LTE1LjRzLTguNS41LTEzLjUtNS43Yy00LjYtNi01LTguNi0xLjQtMTAuMiAzLjgtMiAzOS04LjMgNjMuMi03LjUgMCAwIDguNS0xIDE2IDEzLjcgMCAwIDMuNCA2LTIuNiA3LjRNNTExIDE4Ny44Yy0zIDcuMy0xMS4zIDE1LTIxLjMgMTAuMy0xMC4yLTQuOC0yNi4zLTM3LjYtMjYuMy0zNy42cy02LTEyLjItNy4yLTExLjhjMCAwLTEuMy0yLjQtMiAxMS0xIDEzLjMuMiAzOS4yLTUuMyA0My4zLTUgNC0xMSAyLjMtMTQuNC0yLjQtMi44LTQuNy00LTE2LTIuNC0zNS43IDEuOC0xOS43IDYuMy00MC43IDEyLTQ3LjIgNi02LjYgMTAuOC0xLjggMTIuNiAwIDAgMCA3LjcgNyAyMC43IDI3LjdsMi4yIDMuOHMxMS44IDE5LjcgMTMgMTkuNmMwIDAgMSAxIDEuOC4yIDEuMi0uMy44LTYuNy44LTYuN1M0OTMgMTQxIDQ4MiAxMDVjMCAwLTEuNi00LjYtLjUtOSAxLTQgNS4zLTIgNS4zLTJzMTYuNiA4IDI0LjcgMzVjOCAyNyAyLjYgNTEuNS0uNCA1OC44TTQyOS42IDExOC41Yy0xLjYgMi44LTIuMyA2LjctOS4yIDcuOCAwIDAtNjcgNC43LTcwLjMgOS40IDAgMC0yLjIgMi44IDEuNCAzLjUgMy44LjggMTkgMi44IDI2LjIgMy4yIDcuOCAwIDM0IC4yIDQzLjYgMTIgMCAwIDUuNSA1LjYgNS4zIDE4LjMtLjIgMTMtMi41IDE3LjYtNy42IDIyLjMtNS4zIDQuNC01MC43IDI0LjgtODAtNi40IDAgMC0xMy40LTE1IDQuNy0yNi40IDAgMCAxMy04IDQ2LjMgMS4zIDAgMCAxMCAzLjYgOS42IDcuMy0uNiA0LTguMyA4LTE5LjUgNy44LTEwLjgtLjMtMTguOC01LjUtMTcuMi00LjYgMS41LjUtMTEuNy02LjQtMTUuOC0xLjctNCA0LjQtMyA3IDEgOS43IDEwIDUuOCA0OS4zIDMuNyA2MS05LjQgMCAwIDQuNy01LjMtMi40LTkuNi03LTQtMjcuNC02LjUtMzUuMy02LjgtNy41LS40LTM1LjYgMC0zOS44LTcuMyAwIDAtNC01LjIuNC0xOS40IDQuNi0xNSAzNy4zLTIwLjggNTEuNS0yMiAwIDAgMzktMS42IDQ2LjMgNi40IDAgMCAxIDEuOC0uMiA0LjVNMzE5IDIwNi40Yy00LjcgMy41LTE0LjcgMi0xNy42LTItMi44LTMuNS0zLjgtMTcuMy0zLjMtMzkgLjctMjIuMiAxLTQ5LjQgNi01My44IDUtNC4zIDgtLjUgMTAgMi40IDIgMyA0LjYgNi4zIDUgMTMuMi42IDcgMi4zIDQzIDIuMyA0M3MyLjIgMzIuOC0yLjMgMzYuMk0zMjkgODkuNGMtMTMuOCA0LjctMjMuMiAzLjItMzEuMi0uMy0zLjUgNi4zLTUuNiA4LjItOC4yIDguNi00IC40LTcuNS02LTgtOC0uOC0xLjUtMi43LTQuMi0uNC0xMC4zLTcuOC03LTguNC0xNi40LTctMjIuNyAxLjgtNy40IDE1LTM1LjIgNTUtMzguNSAwIDAgMTkuNi0xLjQgMjMgOWguNnMxOSAwIDE4LjYgMTdjMCAxNy0yMSAzOC4yLTQyLjQgNDUuNW0xNy44LTQ4LjdjLTEyLjYgMi0zMiAxOC44LTQxLjMgMzIuNyAxNC4zIDIuNiAzOS4zIDEuNiA1MC41LTIxIDAgMCA1LjMtMTQuMi05LjItMTEuN20tNTUuMyAxMWMtNCA2LjUtNC4yIDEwLjQtMi4zIDEzIDQuNy03IDEzLTE4IDI1LjUtMjYuNi05LjYgMS0xNy43IDUtMjMuMiAxMy42TTYzMi4yIDIwNS43Yy05LjIgMjIuNi0xNyA0NS41LTIxLjUgNzkuOCAwIDAtMSA2LjctNi41IDQuNS01LjUtMi0xNC41LTExLTE2LjUtMjMuNy0yLTE2LjYgNS40LTQ0LjYgMjAuNS03Ni44LTQuNC03LTcuNS0xNy40LTUtMzIgMCAwIDQtMjcgMzEtNTEuNCAwIDAgMy4yLTIuNyA1LTEuOCAyLjIgMSAxLjMgOS42LS41IDE0LTEuNiA0LjItMTMuNiAyNS0xMy42IDI1cy03LjUgMTQuMi01LjQgMjUuM2MxNC4yLTIxLjggNDYuNS02NiA2Ni41LTUyIDEyLjcgOSAxMi43IDM4IDMuMiA1NC44LTcuNSAxMy4zLTI4LjcgNDAuOC01NyAzNC40bTQxLjYtNjhjLTcuNCA4LTIwLjYgMjMuMi0zMSA0My44IDExLTEuMiAyMS43LTcuMyAyNS0xMC40IDUuMy00LjcgMTcuNS0xNy40IDE1LjYtMzQuMiAwIDAtMS4yLTguOC05LjYuOE0yMjYgMjE3LjVjLTM1LjQgMTAuOC02OC44IDUuOC04NyAxLS41IDcuNC0xLjMgMTAuNS0yLjUgMTEuNy0xLjQgMS42LTEzIDguMi0xOS40LTEuMi0yLjgtNC41LTQuMi0xMi42LTUtMjAtNDEtMTguNi02MC00Ni02MC42LTQ3LTEtMS0xMC4zLTEwLjctMS0yMi43IDguNy0xMC44IDM3LjUtMjEuNyA2My4zLTI2IDEtMjIgMy40LTM5IDYuNS00Ni41IDMuNy05IDguNC0xIDEyLjYgNSAzLjQgNC41IDUuNSAyMy44IDUuNyAzOS4yIDE2LjgtLjggMjcgLjQgNDUuNyA0IDI0LjYgNC4yIDQxIDE2LjggMzkuNyAzMS0xLjIgMTQtMTQgMTkuOC0xOSAyMC4yLTUgLjQtMTMtMy4zLTEzLTMuMy01LjYtMi42LS41LTUgNi03LjggNy4yLTMuNSA1LjYtNyA1LjYtNy0yLjYtOC0zNC41LTEzLjMtNjYuMi0xMy4zIDAgMTcuNS43IDQ2LjUgMS4yIDYzLjQgMjIuMiA0LjIgMzguOCAzLjMgMzguOCAzLjNzODEtMi4zIDgzLjMtNTRjMi41LTUxLjgtODEtMTAxLjQtMTQyLjUtMTE3QzU2LjggMTQuNCAyMiAyNS44IDE5IDI3LjNjLTMuMyAxLjYtLjMgMi4yLS4zIDIuMlMyMiAzMCAyOCAzMmM2IDIgMS4yIDUgMS4yIDVDMTguNyA0MC42IDcgMzguNSA0LjcgMzMuN2MtMi4zLTQuNyAxLjUtOSA2LTE1LjMgNC4yLTYuNSA5LTYuMyA5LTYuMyA3Ni0yNi41IDE2OC44IDIxIDE2OC44IDIxIDg2LjggNDMuOCAxMDEuNiA5NS4zIDEwMCAxMTUuMy0xLjQgMTkuNy05IDUzLTYyLjUgNjkuMk01OSAxNDZjLTguNiA0LTIuNiAxMC40LTIuNiAxMC40IDE2LjIgMTcuMyAzNiAyOC4yIDU1IDM1IDIuMi0zMCAyLTQwLjcgMi01NS44Qzg0IDEzNy42IDY3IDE0Mi40IDU5IDE0NiIvPiA8L3N2Zz4=", + "body": [ + { + "type": "TextBlock", + "text": "This card must have a background image parsed from a dataUri", + "wrap": true + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Cover.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Cover.svg.json new file mode 100644 index 000000000..3b4dbb76d --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Cover.svg.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://adaptivecards.io/content/adaptive-card.svg", + "verticalAlignment": "center", + "horizontalAlignment": "center" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Repeat.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Repeat.svg.json new file mode 100644 index 000000000..2ea83bd9b --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.Repeat.svg.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://adaptivecards.io/content/adaptive-card.svg", + "fillMode": "repeat" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatHorizontally.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatHorizontally.svg.json new file mode 100644 index 000000000..a3447e7ee --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatHorizontally.svg.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "repeatHorizontally", + "verticalAlignment": "center" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatVertically.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatVertically.svg.json new file mode 100644 index 000000000..704e02e97 --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.RepeatVertically.svg.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "repeatVertically", + "horizontalAlignment": "center" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.svg.json new file mode 100644 index 000000000..f32188b41 --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillMode.svg.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "repeat" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillVerticalAlignment.svg.json b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillVerticalAlignment.svg.json new file mode 100644 index 000000000..a3447e7ee --- /dev/null +++ b/samples/v1.6/Elements/AdaptiveCard.BackgroundImage.FillVerticalAlignment.svg.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.6", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "repeatHorizontally", + "verticalAlignment": "center" + }, + "body": [ + { + "type": "TextBlock", + "text": "Text in body" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 1" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "Text in Column 2" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "Text 1 in Container" + }, + { + "type": "TextBlock", + "text": "Text 2 in Container" + }, + { + "type": "TextBlock", + "text": "Text 3 in Container" + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/v1.6/Elements/Column.BackgroundImage.svg.json b/samples/v1.6/Elements/Column.BackgroundImage.svg.json new file mode 100644 index 000000000..bbecddf9a --- /dev/null +++ b/samples/v1.6/Elements/Column.BackgroundImage.svg.json @@ -0,0 +1,141 @@ +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.6", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "width": "auto" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "verticalAlignment": "Center" + }, + "width": "stretch" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "width": "auto" + } + ] + }, + { + "type": "TextBlock", + "text": "You can even repeat the background image..." + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "Repeat" + }, + "width": "stretch" + }, + { + "type": "Column", + "horizontalAlignment": "Center", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "Center", + "text": "Those are some neat arrows", + "wrap": true + } + ], + "width": "stretch" + } + ] + }, + { + "type": "TextBlock", + "text": "Horizontal repeat..." + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatHorizontally" + }, + "width": "stretch" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatHorizontally", + "verticalAlignment": "Center" + }, + "width": "stretch" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatHorizontally", + "verticalAlignment": "Bottom" + }, + "width": "stretch" + } + ] + }, + { + "type": "TextBlock", + "text": "Vertical repeat..." + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatVertically" + }, + "width": "stretch" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatVertically", + "horizontalAlignment": "Center" + }, + "width": "stretch" + }, + { + "type": "Column", + "minHeight": "50px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatVertically", + "horizontalAlignment": "Right" + }, + "width": "stretch" + } + ] + } + ] +} diff --git a/samples/v1.6/Elements/Container.BackgroundImage.svg.json b/samples/v1.6/Elements/Container.BackgroundImage.svg.json new file mode 100644 index 000000000..06ce8db1d --- /dev/null +++ b/samples/v1.6/Elements/Container.BackgroundImage.svg.json @@ -0,0 +1,47 @@ +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.6", + "body": [ + { + "type": "Container", + "minHeight": "150px", + "backgroundImage": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "items": [ + { + "type": "TextBlock", + "text": "What a beautiful background" + } + ] + }, + { + "type": "TextBlock", + "text": "They can even repeat a bunch of different ways..." + }, + { + "type": "Container", + "minHeight": "100px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "Repeat" + } + }, + { + "type": "Container", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatHorizontally", + "verticalAlignment": "Center" + } + }, + { + "type": "Container", + "minHeight": "100px", + "backgroundImage": { + "url": "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg", + "fillMode": "RepeatVertically", + "horizontalAlignment": "Center" + } + } + ] +} diff --git a/source/uwp/SharedRenderer/lib/AdaptiveImageRenderer.cpp b/source/uwp/SharedRenderer/lib/AdaptiveImageRenderer.cpp index e39b3c66a..9ed01f53d 100644 --- a/source/uwp/SharedRenderer/lib/AdaptiveImageRenderer.cpp +++ b/source/uwp/SharedRenderer/lib/AdaptiveImageRenderer.cpp @@ -36,15 +36,6 @@ namespace winrt::AdaptiveCards::Rendering::Xaml_Rendering::implementation namespace AdaptiveCards::Rendering::Xaml_Rendering { - auto inline GetDispatcher(winrt::ImageSource const &imageSource) - { -#ifdef USE_WINUI3 - return imageSource.DispatcherQueue(); -#else - return imageSource.Dispatcher(); -#endif - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT! Methods below here are actually XamlBuilder methods. They're defined here because they're only used @@ -71,7 +62,7 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering return nullptr; } - auto isImageSvg = IsSvgImage(HStringToUTF8(url)); + auto isImageSvg = IsSvgImage(imageUrl); uint32_t pixelWidth = adaptiveImage.PixelWidth(); uint32_t pixelHeight = adaptiveImage.PixelHeight(); @@ -289,57 +280,6 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering adaptiveCardElement, selectAction, renderContext, frameworkElement, XamlHelpers::SupportsInteractivity(hostConfig), true); } - winrt::Windows::Foundation::Size XamlBuilder::ParseSizeOfSVGImageFromXmlString(winrt::hstring const& content) - { - // Parse the size from the XamlDocument as XML - winrt::XmlDocument xmlDoc; - - xmlDoc.LoadXml(content); - - if (xmlDoc) - { - auto rootElement = xmlDoc.DocumentElement(); - - // Root element must be an SVG - if (rootElement.NodeName() == L"svg") - { - auto heightAttribute = rootElement.GetAttribute(L"height"); - auto widthAttribute = rootElement.GetAttribute(L"width"); - - double height{0.0}; - double width{0.0}; - - if (!heightAttribute.empty()) - { - height = TryHStringToDouble(heightAttribute).value_or(0.0); - } - - if (!widthAttribute.empty()) - { - width = TryHStringToDouble(widthAttribute).value_or(0.0); - } - - return {static_cast(width), static_cast(height)}; - } - } - - return {}; - } - - winrt::IAsyncOperation XamlBuilder::ParseSizeOfSVGImageFromStreamAsync(winrt::IRandomAccessStream const stream) - { - auto inputStream = stream.GetInputStreamAt(0); - auto dataReader = winrt::DataReader(inputStream); - - // Load the data from the stream - uint32_t numBytesLoaded = co_await dataReader.LoadAsync(static_cast(stream.Size())); - - // Read the data as a string - winrt::hstring svgString = dataReader.ReadString(numBytesLoaded); - - co_return ParseSizeOfSVGImageFromXmlString(svgString); - } - winrt::IAsyncOperation XamlBuilder::ResolveToStreamAsync(winrt::Uri const imageUrl, winrt::AdaptiveCardResourceResolvers const resolvers, bool const isImageSvg) { @@ -688,14 +628,7 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering } } } - - boolean XamlBuilder::IsSvgImage(std::string url) - { - // Question: is this check sufficient? - auto foundSvg = url.find("svg"); - return !(foundSvg == std::string::npos); - } - + void XamlBuilder::SetRasterizedPixelHeight(winrt::ImageSource const& imageSource, double const& imageSize) { if (auto image = imageSource.try_as()) { diff --git a/source/uwp/SharedRenderer/lib/TileControl.cpp b/source/uwp/SharedRenderer/lib/TileControl.cpp index 6689c7314..a17813a13 100644 --- a/source/uwp/SharedRenderer/lib/TileControl.cpp +++ b/source/uwp/SharedRenderer/lib/TileControl.cpp @@ -32,6 +32,25 @@ namespace winrt::AdaptiveCards::Rendering::Xaml_Rendering::implementation } } } + + void TileControl::SvgImageOpened(const IInspectable& /* sender */, const winrt::SvgImageSourceOpenedEventArgs& /* args */) + { + auto uiElement = m_resolvedImage; + + // Do we need to throw/log if we fail here? + if (const auto image = m_resolvedImage.try_as()) + { + if (const auto imageSource = image.Source()) + { + if (const auto svgImageSource = imageSource.try_as()) + { + m_imageSize = {(float)svgImageSource.RasterizePixelWidth(), (float)svgImageSource.RasterizePixelHeight()}; + RefreshContainerTile(); + } + } + } + } + void TileControl::LoadImageBrush(winrt::UIElement const& uielement) { m_resolvedImage = uielement; @@ -46,6 +65,12 @@ namespace winrt::AdaptiveCards::Rendering::Xaml_Rendering::implementation m_imageOpenedRevoker = bitmapImage.ImageOpened(winrt::auto_revoke, {this, &TileControl::ImageOpened}); m_brushXaml.ImageSource(imageSource); } + else if (const auto svgImageSource = imageSource.try_as()) + { + m_svgImageOpenedRevoker.revoke(); + m_svgImageOpenedRevoker = svgImageSource.Opened(winrt::auto_revoke, {this, &TileControl::SvgImageOpened}); + m_brushXaml.ImageSource(imageSource); + } } } } diff --git a/source/uwp/SharedRenderer/lib/TileControl.h b/source/uwp/SharedRenderer/lib/TileControl.h index ac9be83ed..25ac84576 100644 --- a/source/uwp/SharedRenderer/lib/TileControl.h +++ b/source/uwp/SharedRenderer/lib/TileControl.h @@ -34,6 +34,7 @@ namespace winrt::AdaptiveCards::Rendering::Xaml_Rendering::implementation private: void RefreshContainerTile(); void ImageOpened(const IInspectable& sender, const winrt::RoutedEventArgs& args); + void SvgImageOpened(const IInspectable& sender, const winrt::SvgImageSourceOpenedEventArgs& args); // Fields /* winrt::FrameworkElement m_rootElement;*/ @@ -48,6 +49,7 @@ namespace winrt::AdaptiveCards::Rendering::Xaml_Rendering::implementation // Revokers winrt::BitmapImage::ImageOpened_revoker m_imageOpenedRevoker; + winrt::SvgImageSource::Opened_revoker m_svgImageOpenedRevoker; }; } diff --git a/source/uwp/SharedRenderer/lib/Util.cpp b/source/uwp/SharedRenderer/lib/Util.cpp index f8af69853..88b423c10 100644 --- a/source/uwp/SharedRenderer/lib/Util.cpp +++ b/source/uwp/SharedRenderer/lib/Util.cpp @@ -657,3 +657,71 @@ std::string ExtractSvgDataFromUri(winrt::Windows::Foundation::Uri const& imageUr } return data; } + +winrt::Windows::Foundation::Size ParseSizeOfSVGImageFromXmlString(winrt::hstring const& content) +{ + // Parse the size from the XamlDocument as XML + winrt::XmlDocument xmlDoc; + + xmlDoc.LoadXml(content); + + if (xmlDoc) + { + auto rootElement = xmlDoc.DocumentElement(); + + // Root element must be an SVG + if (rootElement.NodeName() == L"svg") + { + auto heightAttribute = rootElement.GetAttribute(L"height"); + auto widthAttribute = rootElement.GetAttribute(L"width"); + + double height{0.0}; + double width{0.0}; + + if (!heightAttribute.empty()) + { + height = TryHStringToDouble(heightAttribute).value_or(0.0); + } + + if (!widthAttribute.empty()) + { + width = TryHStringToDouble(widthAttribute).value_or(0.0); + } + + return {static_cast(width), static_cast(height)}; + } + } + + return {}; +} + +winrt::IAsyncOperation ParseSizeOfSVGImageFromStreamAsync(winrt::IRandomAccessStream const stream) +{ + auto inputStream = stream.GetInputStreamAt(0); + auto dataReader = winrt::DataReader(inputStream); + + // Load the data from the stream + uint32_t numBytesLoaded = co_await dataReader.LoadAsync(static_cast(stream.Size())); + + // Read the data as a string + winrt::hstring svgString = dataReader.ReadString(numBytesLoaded); + + co_return ParseSizeOfSVGImageFromXmlString(svgString); +} + +bool IsSvgImage(winrt::Windows::Foundation::Uri const& imageUrl) +{ + auto imagePath = HStringToUTF8(imageUrl.Path()); + + if (imageUrl.SchemeName() == L"data") + { + // Find the position of the first semicolon + size_t semicolonPos = imagePath.find(';'); + + // Check if "svg" is present in the substring from the start to the semicolon + return imagePath.substr(0, semicolonPos).find("svg") != std::string::npos; + } + + // Check if the file extension is ".svg" + return imagePath.substr(imagePath.find_last_of('.') + 1) == "svg"; +} diff --git a/source/uwp/SharedRenderer/lib/Util.h b/source/uwp/SharedRenderer/lib/Util.h index f013476f3..9a18a6d31 100644 --- a/source/uwp/SharedRenderer/lib/Util.h +++ b/source/uwp/SharedRenderer/lib/Util.h @@ -225,3 +225,18 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering } std::string ExtractSvgDataFromUri(winrt::Windows::Foundation::Uri const& imageUrl); + +winrt::Windows::Foundation::Size ParseSizeOfSVGImageFromXmlString(winrt::hstring const& content); + +winrt::IAsyncOperation ParseSizeOfSVGImageFromStreamAsync(winrt::IRandomAccessStream const stream); + +bool IsSvgImage(winrt::Windows::Foundation::Uri const& imageUrl); + +auto inline GetDispatcher(winrt::ImageSource const& imageSource) +{ +#ifdef USE_WINUI3 + return imageSource.DispatcherQueue(); +#else + return imageSource.Dispatcher(); +#endif +} diff --git a/source/uwp/SharedRenderer/lib/XamlBuilder.h b/source/uwp/SharedRenderer/lib/XamlBuilder.h index a7075f932..04a33d40e 100644 --- a/source/uwp/SharedRenderer/lib/XamlBuilder.h +++ b/source/uwp/SharedRenderer/lib/XamlBuilder.h @@ -97,10 +97,6 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering winrt::ImageSource CreateImageSource(bool isImageSvg); - winrt::Windows::Foundation::Size XamlBuilder::ParseSizeOfSVGImageFromXmlString(winrt::hstring const& content); - - winrt::IAsyncOperation XamlBuilder::ParseSizeOfSVGImageFromStreamAsync(winrt::IRandomAccessStream const stream); - winrt::IAsyncOperation XamlBuilder::ResolveToStreamAsync( winrt::Uri const uri, winrt::AdaptiveCardResourceResolvers const resolvers, bool const isImageSvg); @@ -116,8 +112,6 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering winrt::ImageSource imageSource, ImageProperties const properties); - boolean IsSvgImage(std::string url); - void FireAllImagesLoaded(); void FireImagesLoadingHadError(); diff --git a/source/uwp/SharedRenderer/lib/XamlHelpers.cpp b/source/uwp/SharedRenderer/lib/XamlHelpers.cpp index e76d13c38..551ed6c8d 100644 --- a/source/uwp/SharedRenderer/lib/XamlHelpers.cpp +++ b/source/uwp/SharedRenderer/lib/XamlHelpers.cpp @@ -6,6 +6,7 @@ #include "TileControl.h" #include "AdaptiveBase64Util.h" #include "WholeItemsPanel.h" +#include "Util.h" namespace AdaptiveCards::Rendering::Xaml_Rendering::XamlHelpers { @@ -242,24 +243,41 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering::XamlHelpers return content; } - winrt::Image CreateBackgroundImage(winrt::AdaptiveRenderContext const& renderContext, winrt::hstring const& url) + winrt::Image CreateBackgroundImage(winrt::AdaptiveRenderContext const& renderContext, + winrt::TileControl const& tileControl, + bool IsSvg, + winrt::Uri imageUrl) { try { - auto imageUrl = GetUrlFromString(renderContext.HostConfig(), url); - - if (imageUrl.SchemeName() == L"data") + if (IsSvg) { - return RenderImageFromDataUri(imageUrl); + winrt::SvgImageSource svgImageSource{}; + + ConfigureSvgImageSourceAsync(imageUrl, svgImageSource, tileControl); + + winrt::Image backgroundImage; + backgroundImage.Source(svgImageSource); + + return backgroundImage; + } + else + { + if (imageUrl.SchemeName() == L"data") + { + return RenderImageFromDataUri(imageUrl); + } + else + { + winrt::BitmapImage bitmapImage{}; + bitmapImage.UriSource(imageUrl); + + winrt::Image backgroundImage; + backgroundImage.Source(bitmapImage); + + return backgroundImage; + } } - - winrt::BitmapImage bitmapImage{}; - bitmapImage.UriSource(imageUrl); - - winrt::Image backgroundImage; - backgroundImage.Source(bitmapImage); - - return backgroundImage; } catch (winrt::hresult_error const& ex) { @@ -268,21 +286,93 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering::XamlHelpers } } + winrt::fire_and_forget ConfigureSvgImageSourceAsync(winrt::Uri imageUrl, + winrt::SvgImageSource svgImageSource, + winrt::TileControl tileControl) + { + auto weakImageSource{winrt::make_weak(svgImageSource)}; + auto weakTileControl{winrt::make_weak(tileControl)}; + + if (imageUrl.SchemeName() == L"data") + { + auto imagePath = HStringToUTF8(imageUrl.Path()); + auto foundBase64 = imagePath.find("base64"); + winrt::DataWriter dataWriter{winrt::InMemoryRandomAccessStream{}}; + + if (foundBase64 != std::string::npos) + { + // Decode base 64 string + std::string data = AdaptiveBase64Util::ExtractDataFromUri(imagePath); + std::vector decodedData = AdaptiveBase64Util::Decode(data); + dataWriter.WriteBytes(std::vector{decodedData.begin(), decodedData.end()}); + } + else + { + std::string data = ExtractSvgDataFromUri(imageUrl); + dataWriter.WriteBytes(std::vector{data.begin(), data.end()}); + } + + co_await dataWriter.StoreAsync(); + if (auto strongImageSource = weakImageSource.get()) + { + auto stream = dataWriter.DetachStream().try_as(); + stream.Seek(0); + auto sizeParseOp{ParseSizeOfSVGImageFromStreamAsync(stream)}; + co_await wil::resume_foreground(GetDispatcher(strongImageSource)); + auto size = co_await sizeParseOp; + strongImageSource.RasterizePixelHeight(size.Height); + strongImageSource.RasterizePixelWidth(size.Width); + co_await strongImageSource.SetSourceAsync(stream); + winrt::Image image; + image.Source(strongImageSource); + if (auto strongTileControl = weakTileControl.get()) + { + strongTileControl.LoadImageBrush(image); + } + } + co_return; + } + + winrt::HttpClient httpClient; + auto getOperation = co_await httpClient.GetAsync(imageUrl); + auto readOperation = co_await getOperation.Content().ReadAsStringAsync(); + auto size{ParseSizeOfSVGImageFromXmlString(readOperation)}; + + if (auto strongImageSource = weakImageSource.get()) + { + co_await wil::resume_foreground(GetDispatcher(strongImageSource)); + strongImageSource.RasterizePixelHeight(size.Height); + strongImageSource.RasterizePixelWidth(size.Width); + winrt::Image image; + image.Source(strongImageSource); + strongImageSource.UriSource(imageUrl); + if (auto strongTileControl = weakTileControl.get()) + { + strongTileControl.LoadImageBrush(image); + } + } + } + void ApplyBackgroundToRoot(winrt::Panel const& rootPanel, winrt::AdaptiveBackgroundImage const& adaptiveBackgroundImage, winrt::AdaptiveRenderContext const& renderContext) { + // Creates the background image for all fill modes + auto tileControl = winrt::make(); + auto imageUrl = GetUrlFromString(renderContext.HostConfig(), adaptiveBackgroundImage.Url()); + bool IsSvg = IsSvgImage(imageUrl); + // In order to reuse the image creation code paths, we simply create an adaptive card // image element and then build that into xaml and apply to the root. - if (const auto backgroundImage = CreateBackgroundImage(renderContext, adaptiveBackgroundImage.Url())) + if (const auto backgroundImage = CreateBackgroundImage(renderContext, tileControl, IsSvg, imageUrl)) { - // Creates the background image for all fill modes - auto tileControl = winrt::make(); - // Set IsEnabled to false to avoid generating a tab stop for the background image tile control tileControl.IsEnabled(false); tileControl.BackgroundImage(adaptiveBackgroundImage); - tileControl.LoadImageBrush(backgroundImage); + if (!IsSvg) + { + tileControl.LoadImageBrush(backgroundImage); + } XamlHelpers::AppendXamlElementToPanel(tileControl, rootPanel); diff --git a/source/uwp/SharedRenderer/lib/XamlHelpers.h b/source/uwp/SharedRenderer/lib/XamlHelpers.h index 9f15c8e00..7112fde1d 100644 --- a/source/uwp/SharedRenderer/lib/XamlHelpers.h +++ b/source/uwp/SharedRenderer/lib/XamlHelpers.h @@ -197,4 +197,8 @@ namespace AdaptiveCards::Rendering::Xaml_Rendering::XamlHelpers SeparatorParemeters GetSeparatorParameters(winrt::IAdaptiveCardElement const& element, winrt::AdaptiveHostConfig const& hostConfig); winrt::Image RenderImageFromDataUri(winrt::Uri const& imageUrl); + + winrt::fire_and_forget ConfigureSvgImageSourceAsync(winrt::Uri imageUrl, + winrt::SvgImageSource svgImageSource, + winrt::TileControl tileControl); }