From 0011cace21a97705534dbf3a2fbaeae66d0d1670 Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Thu, 5 Feb 2026 17:13:06 +0800 Subject: [PATCH 1/3] Add AccessControl timeline provider sample --- .../AccessControlTimelineProvider.cs | 76 +++++++++++++++++++ .../AccessControlTimelineProviderBuilder.cs | 21 +++++ .../AccessTimelineEvent.cs | 52 +++++++++++++ .../AlarmTimelineProvider.cs | 35 +++++---- .../AlarmTimelineProviderBuilder.cs | 4 +- ....AccessControlTimelineProviderBuilder.cert | 5 ++ .../TimelineProviderSample/SampleModule.cs | 31 +++++++- 7 files changed, 203 insertions(+), 21 deletions(-) create mode 100644 Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs create mode 100644 Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProviderBuilder.cs create mode 100644 Samples/Workspace SDK/TimelineProviderSample/AccessTimelineEvent.cs create mode 100644 Samples/Workspace SDK/TimelineProviderSample/Certificates/Genetec.Dap.CodeSamples.AccessControlTimelineProviderBuilder.cert diff --git a/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs new file mode 100644 index 00000000..fd3af069 --- /dev/null +++ b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs @@ -0,0 +1,76 @@ +// Copyright 2026 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using Sdk; +using Sdk.Events.AccessPoint; +using Sdk.Queries; +using Sdk.Queries.AccessControl; +using Sdk.Workspace; +using Sdk.Workspace.Components.TimelineProvider; +using Sdk.Workspace.Pages.Contents; +using System; +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +public class AccessControlTimelineProvider : TimelineProvider, IDisposable +{ + private readonly Workspace m_workspace; + + public AccessControlTimelineProvider(Workspace workspace) + { + m_workspace = workspace; + m_workspace.Sdk.EventReceived += OnEventReceived; + } + + public override void Query(ContentGroup contentGroup, DateTime startTime, DateTime endTime) + { + Task.Run(async () => + { + var query = (CardholderActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.CardholderActivity); + query.TimeRange.SetTimeRange(startTime, endTime); + query.Events.Clear(); + query.Events.Add(EventType.AccessGranted); + query.Events.Add(EventType.AccessRefused); + + QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null); + + var events = result.Data.AsEnumerable().Select(row => + { + var timestamp = row.Field(AccessControlReportQuery.TimestampColumnName); + var cardholderGuid = row.Field(AccessControlReportQuery.CardholderGuidColumnName); + var eventType = row.Field(AccessControlReportQuery.EventTypeColumnName); + + return new AccessTimelineEvent(cardholderGuid, timestamp, eventType == EventType.AccessGranted); + }); + + InsertEvents(events); + OnQueryCompleted(); + }); + } + + private void OnEventReceived(object sender, EventReceivedEventArgs e) + { + switch (e.EventType) + { + case EventType.AccessGranted: + case EventType.AccessRefused: + if (e.Event is AccessEvent accessEvent) + { + var timelineEvent = new AccessTimelineEvent( + accessEvent.Cardholder, + accessEvent.Timestamp, + accessEvent.Type == EventType.AccessGranted); + InsertEvent(timelineEvent); + } + break; + } + } + + public void Dispose() + { + m_workspace.Sdk.EventReceived -= OnEventReceived; + } +} diff --git a/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProviderBuilder.cs b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProviderBuilder.cs new file mode 100644 index 00000000..da0293a0 --- /dev/null +++ b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProviderBuilder.cs @@ -0,0 +1,21 @@ +// Copyright 2026 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using Sdk.Workspace.Components.TimelineProvider; +using System; + +public class AccessControlTimelineProviderBuilder : TimelineProviderBuilder +{ + public override string Name => nameof(AccessControlTimelineProviderBuilder); + + public override string Title => "Access Control Events"; + + public override Guid UniqueId { get; } = new Guid("8A3C5F12-9D4E-4B7A-B2C1-6E8F0A1D3C5B"); // Replace with your own unique GUID + + public override TimelineProvider CreateProvider() + { + return new AccessControlTimelineProvider(Workspace); + } +} diff --git a/Samples/Workspace SDK/TimelineProviderSample/AccessTimelineEvent.cs b/Samples/Workspace SDK/TimelineProviderSample/AccessTimelineEvent.cs new file mode 100644 index 00000000..acb51134 --- /dev/null +++ b/Samples/Workspace SDK/TimelineProviderSample/AccessTimelineEvent.cs @@ -0,0 +1,52 @@ +// Copyright 2026 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using Sdk; +using Sdk.Workspace.Components.TimelineProvider; +using System; +using System.Windows; +using System.Windows.Media; + +public class AccessTimelineEvent : TimelineEvent +{ + private static readonly Size s_size = new Size(16, 16); + + private static readonly ImageSource s_grantedImage; + private static readonly ImageSource s_refusedImage; + + private readonly bool m_isGranted; + + static AccessTimelineEvent() + { + s_grantedImage = EventType.AccessGranted.GetIcon(); + s_refusedImage = EventType.AccessRefused.GetIcon(); + } + + public AccessTimelineEvent(Guid cardholderGuid, DateTime timestamp, bool isGranted) : base(timestamp) + { + CardholderGuid = cardholderGuid; + m_isGranted = isGranted; + } + + public Guid CardholderGuid { get; } + + public override TimelineVisual GetVisual(Rect constraint, double msPerPixel) + { + var drawingVisual = new DrawingVisual(); + + DrawingContext drawingContext = drawingVisual.RenderOpen(); + + ImageSource image = m_isGranted ? s_grantedImage : s_refusedImage; + drawingContext.DrawImage(image, new Rect(new Point(constraint.X, (constraint.Height - 16) / 2), s_size)); + + drawingContext.Close(); + + return new TimelineVisual(drawingVisual) + { + AlignmentY = AlignmentY.Center, + AlignmentX = AlignmentX.Center + }; + } +} diff --git a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs index 7a42370a..c1189716 100644 --- a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs +++ b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs @@ -1,4 +1,4 @@ -// Copyright 2025 Genetec Inc. +// Copyright 2026 Genetec Inc. // Licensed under the Apache License, Version 2.0 namespace Genetec.Dap.CodeSamples; @@ -13,24 +13,14 @@ namespace Genetec.Dap.CodeSamples; using Sdk.Workspace.Components.TimelineProvider; using Sdk.Workspace.Pages.Contents; -public class AlarmTimelineProvider : TimelineProvider +public class AlarmTimelineProvider : TimelineProvider, IDisposable { private readonly Workspace m_workspace; public AlarmTimelineProvider(Workspace workspace) { m_workspace = workspace; - - workspace.Sdk.AlarmAcknowledged += OnAlarmAcknowledged; - - void OnAlarmAcknowledged(object sender, AlarmAcknowledgedEventArgs e) - { - foreach (AlarmTimelineEvent timelineEvent in GetEvents().OfType() - .Where(timelineEvent => timelineEvent.AlarmGuid == e.AlarmGuid && timelineEvent.InstanceId == e.InstanceId)) - { - RemoveEvent(timelineEvent); - } - } + m_workspace.Sdk.AlarmAcknowledged += OnAlarmAcknowledged; } public override void Query(ContentGroup contentGroup, DateTime startTime, DateTime endTime) @@ -50,11 +40,26 @@ public override void Query(ContentGroup contentGroup, DateTime startTime, DateTi var alarmGuid = row.Field(AlarmActivityQuery.AlarmColumnName); var instanceId = row.Field(AlarmActivityQuery.InstanceIdColumnName); var triggerTime = row.Field(AlarmActivityQuery.TriggerTimeColumnName); - + return new AlarmTimelineEvent(alarmGuid, instanceId, triggerTime); }); InsertEvents(events); + OnQueryCompleted(); }); } -} \ No newline at end of file + + private void OnAlarmAcknowledged(object sender, AlarmAcknowledgedEventArgs e) + { + foreach (AlarmTimelineEvent timelineEvent in GetEvents().OfType() + .Where(t => t.AlarmGuid == e.AlarmGuid && t.InstanceId == e.InstanceId)) + { + RemoveEvent(timelineEvent); + } + } + + public void Dispose() + { + m_workspace.Sdk.AlarmAcknowledged -= OnAlarmAcknowledged; + } +} diff --git a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProviderBuilder.cs b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProviderBuilder.cs index 1c66d20c..26074934 100644 --- a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProviderBuilder.cs +++ b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProviderBuilder.cs @@ -3,8 +3,8 @@ namespace Genetec.Dap.CodeSamples; -using System; using Sdk.Workspace.Components.TimelineProvider; +using System; public class AlarmTimelineProviderBuilder : TimelineProviderBuilder { @@ -12,7 +12,7 @@ public class AlarmTimelineProviderBuilder : TimelineProviderBuilder public override string Title => "Active Alarms"; - public override Guid UniqueId { get; } = new Guid("4765D714-2BD6-42A8-99E3-0A0767C76321"); + public override Guid UniqueId { get; } = new Guid("4765D714-2BD6-42A8-99E3-0A0767C76321"); // Replace with your own unique GUID public override TimelineProvider CreateProvider() { diff --git a/Samples/Workspace SDK/TimelineProviderSample/Certificates/Genetec.Dap.CodeSamples.AccessControlTimelineProviderBuilder.cert b/Samples/Workspace SDK/TimelineProviderSample/Certificates/Genetec.Dap.CodeSamples.AccessControlTimelineProviderBuilder.cert new file mode 100644 index 00000000..b9a1fc92 --- /dev/null +++ b/Samples/Workspace SDK/TimelineProviderSample/Certificates/Genetec.Dap.CodeSamples.AccessControlTimelineProviderBuilder.cert @@ -0,0 +1,5 @@ + + Genetec + Demo Certificate for SDK Development only + KxsD11z743Hf5Gq9mv3+5ekxzemlCiUXkTFY5ba1NOGcLCmGstt2n0zYE9NsNimv + diff --git a/Samples/Workspace SDK/TimelineProviderSample/SampleModule.cs b/Samples/Workspace SDK/TimelineProviderSample/SampleModule.cs index d23fdd60..05188f51 100644 --- a/Samples/Workspace SDK/TimelineProviderSample/SampleModule.cs +++ b/Samples/Workspace SDK/TimelineProviderSample/SampleModule.cs @@ -1,24 +1,47 @@ -// Copyright 2025 Genetec Inc. +// Copyright 2026 Genetec Inc. // Licensed under the Apache License, Version 2.0 namespace Genetec.Dap.CodeSamples; +using Genetec.Sdk.Workspace.Components; using Sdk; using Sdk.Workspace.Modules; public class SampleModule : Module { + private AlarmTimelineProviderBuilder m_alarmBuilder; + private AccessControlTimelineProviderBuilder m_accessControlBuilder; + public override void Load() { if (Workspace.ApplicationType == ApplicationType.SecurityDesk) { - var builder = new AlarmTimelineProviderBuilder(); - builder.Initialize(Workspace); - Workspace.Components.Register(builder); + m_alarmBuilder = new AlarmTimelineProviderBuilder(); + Register(m_alarmBuilder); + + m_accessControlBuilder = new AccessControlTimelineProviderBuilder(); + Register(m_accessControlBuilder); + + void Register(Component component) + { + component.Initialize(Workspace); + Workspace.Components.Register(component); + } } } public override void Unload() { + if (m_alarmBuilder != null) + { + Workspace.Components.Unregister(m_alarmBuilder); + m_alarmBuilder = null; + } + + if (m_accessControlBuilder != null) + { + Workspace.Components.Unregister(m_accessControlBuilder); + m_accessControlBuilder = null; + } } } From 423b09a485774085c17b9aa3400c809cdee1425f Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Thu, 5 Feb 2026 23:51:05 +0800 Subject: [PATCH 2/3] fix: improve ImageExtractorSample code quality - Add proper component unregistration in SampleModule.Unload() - Add error handling with MessageBox in SampleImageExtractor - Fix VCardReader to correctly extract FirstName from N: field - Make photo regex more flexible for different vCard formats --- .../SampleImageExtractor.cs | 19 ++++++++++--- .../ImageExtractorSample/SampleModule.cs | 13 ++++++--- .../ImageExtractorSample/VCardReader.cs | 27 +++++++++++++++---- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs b/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs index 9f053b13..71ed29d7 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs @@ -4,6 +4,7 @@ namespace Genetec.Dap.CodeSamples; using System; +using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Win32; @@ -17,16 +18,26 @@ public sealed class SampleImageExtractor : ImageExtractor public override Guid UniqueId { get; } = new Guid("5EDBB0B6-8253-433E-99A1-9021E498437A"); - public override ImageSource GetImage() { - var openFileDialog = new OpenFileDialog + var dialog = new OpenFileDialog { Filter = "vCard files (*.vcf)|*.vcf|All files (*.*)|*.*", - Title = "Open vCard File" + Title = "Select vCard File" }; - return openFileDialog.ShowDialog() == true ? VCardReader.ReadVCard(openFileDialog.FileName)?.Picture : null; + if (dialog.ShowDialog() != true) + return null; + + try + { + return VCardReader.ReadVCard(dialog.FileName)?.Picture; + } + catch (Exception ex) + { + MessageBox.Show($"Failed to read vCard file: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + return null; + } } public override bool SupportsContext(ImageExtractorContext context) diff --git a/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs b/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs index f1f48d88..d443c473 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs @@ -7,16 +7,23 @@ namespace Genetec.Dap.CodeSamples; public class SampleModule : Module { + private SampleImageExtractor m_imageExtractor; + static SampleModule() => AssemblyResolver.Initialize(); public override void Load() { - var component = new SampleImageExtractor(); - component.Initialize(Workspace); - Workspace.Components.Register(component); + m_imageExtractor = new SampleImageExtractor(); + m_imageExtractor.Initialize(Workspace); + Workspace.Components.Register(m_imageExtractor); } public override void Unload() { + if (m_imageExtractor != null) + { + Workspace.Components.Unregister(m_imageExtractor); + m_imageExtractor = null; + } } } \ No newline at end of file diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs index 8209c34f..070770b0 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -16,10 +16,14 @@ public class VCardReader public static VCard ReadVCard(string filePath) { string vCardText = File.ReadAllText(filePath); + + // N: field format is LastName;FirstName;MiddleName;Prefix;Suffix + string[] nameParts = ExtractField(vCardText, "N:").Split(';'); + var vcard = new VCard { - FirstName = ExtractField(vCardText, "FN:"), - LastName = ExtractField(vCardText, "N:").Split(';')[0], // Assuming last name is the first component in the N: field + LastName = nameParts.Length > 0 ? nameParts[0] : string.Empty, + FirstName = nameParts.Length > 1 ? nameParts[1] : string.Empty, Note = ExtractField(vCardText, "NOTE:"), Picture = ExtractPhoto(vCardText) }; @@ -42,12 +46,25 @@ private static List ExtractEmails(string vCardText) private static ImageSource ExtractPhoto(string vCardText) { - Match photoMatch = Regex.Match(vCardText, @"PHOTO;ENCODING=b;TYPE=image/jpeg:(.*?)(\n(?![ \t])|\r\n(?![ \t])|$)", RegexOptions.Singleline); + // Match various vCard photo formats: + // PHOTO;ENCODING=b;TYPE=image/jpeg: + // PHOTO;ENCODING=BASE64;TYPE=JPEG: + // PHOTO;TYPE=JPEG;ENCODING=b: + // PHOTO;ENCODING=b;TYPE=image/png: + var photoMatch = Regex.Match(vCardText, + @"PHOTO;[^:]*(?:ENCODING=(?:b|BASE64))[^:]*:(.*?)(\n(?![ \t])|\r\n(?![ \t])|$)", + RegexOptions.Singleline | RegexOptions.IgnoreCase); + if (photoMatch.Success) { - string base64Data = photoMatch.Groups[1].Value.Trim().Replace("\n", "").Replace("\r", ""); + string base64Data = photoMatch.Groups[1].Value + .Trim() + .Replace("\n", "") + .Replace("\r", ""); + + byte[] imageBytes = Convert.FromBase64String(base64Data); - using var stream = new MemoryStream(Convert.FromBase64String(base64Data)); + using var stream = new MemoryStream(imageBytes); var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = stream; From 2795c8abfebffe1ffddaa4d08c117e331d796747 Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Thu, 5 Feb 2026 23:52:02 +0800 Subject: [PATCH 3/3] Revert "fix: improve ImageExtractorSample code quality" This reverts commit 423b09a485774085c17b9aa3400c809cdee1425f. --- .../SampleImageExtractor.cs | 19 +++---------- .../ImageExtractorSample/SampleModule.cs | 13 +++------ .../ImageExtractorSample/VCardReader.cs | 27 ++++--------------- 3 files changed, 12 insertions(+), 47 deletions(-) diff --git a/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs b/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs index 71ed29d7..9f053b13 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/SampleImageExtractor.cs @@ -4,7 +4,6 @@ namespace Genetec.Dap.CodeSamples; using System; -using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Win32; @@ -18,26 +17,16 @@ public sealed class SampleImageExtractor : ImageExtractor public override Guid UniqueId { get; } = new Guid("5EDBB0B6-8253-433E-99A1-9021E498437A"); + public override ImageSource GetImage() { - var dialog = new OpenFileDialog + var openFileDialog = new OpenFileDialog { Filter = "vCard files (*.vcf)|*.vcf|All files (*.*)|*.*", - Title = "Select vCard File" + Title = "Open vCard File" }; - if (dialog.ShowDialog() != true) - return null; - - try - { - return VCardReader.ReadVCard(dialog.FileName)?.Picture; - } - catch (Exception ex) - { - MessageBox.Show($"Failed to read vCard file: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); - return null; - } + return openFileDialog.ShowDialog() == true ? VCardReader.ReadVCard(openFileDialog.FileName)?.Picture : null; } public override bool SupportsContext(ImageExtractorContext context) diff --git a/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs b/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs index d443c473..f1f48d88 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/SampleModule.cs @@ -7,23 +7,16 @@ namespace Genetec.Dap.CodeSamples; public class SampleModule : Module { - private SampleImageExtractor m_imageExtractor; - static SampleModule() => AssemblyResolver.Initialize(); public override void Load() { - m_imageExtractor = new SampleImageExtractor(); - m_imageExtractor.Initialize(Workspace); - Workspace.Components.Register(m_imageExtractor); + var component = new SampleImageExtractor(); + component.Initialize(Workspace); + Workspace.Components.Register(component); } public override void Unload() { - if (m_imageExtractor != null) - { - Workspace.Components.Unregister(m_imageExtractor); - m_imageExtractor = null; - } } } \ No newline at end of file diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs index 070770b0..8209c34f 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -16,14 +16,10 @@ public class VCardReader public static VCard ReadVCard(string filePath) { string vCardText = File.ReadAllText(filePath); - - // N: field format is LastName;FirstName;MiddleName;Prefix;Suffix - string[] nameParts = ExtractField(vCardText, "N:").Split(';'); - var vcard = new VCard { - LastName = nameParts.Length > 0 ? nameParts[0] : string.Empty, - FirstName = nameParts.Length > 1 ? nameParts[1] : string.Empty, + FirstName = ExtractField(vCardText, "FN:"), + LastName = ExtractField(vCardText, "N:").Split(';')[0], // Assuming last name is the first component in the N: field Note = ExtractField(vCardText, "NOTE:"), Picture = ExtractPhoto(vCardText) }; @@ -46,25 +42,12 @@ private static List ExtractEmails(string vCardText) private static ImageSource ExtractPhoto(string vCardText) { - // Match various vCard photo formats: - // PHOTO;ENCODING=b;TYPE=image/jpeg: - // PHOTO;ENCODING=BASE64;TYPE=JPEG: - // PHOTO;TYPE=JPEG;ENCODING=b: - // PHOTO;ENCODING=b;TYPE=image/png: - var photoMatch = Regex.Match(vCardText, - @"PHOTO;[^:]*(?:ENCODING=(?:b|BASE64))[^:]*:(.*?)(\n(?![ \t])|\r\n(?![ \t])|$)", - RegexOptions.Singleline | RegexOptions.IgnoreCase); - + Match photoMatch = Regex.Match(vCardText, @"PHOTO;ENCODING=b;TYPE=image/jpeg:(.*?)(\n(?![ \t])|\r\n(?![ \t])|$)", RegexOptions.Singleline); if (photoMatch.Success) { - string base64Data = photoMatch.Groups[1].Value - .Trim() - .Replace("\n", "") - .Replace("\r", ""); - - byte[] imageBytes = Convert.FromBase64String(base64Data); + string base64Data = photoMatch.Groups[1].Value.Trim().Replace("\n", "").Replace("\r", ""); - using var stream = new MemoryStream(imageBytes); + using var stream = new MemoryStream(Convert.FromBase64String(base64Data)); var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = stream;