Skip to content

Commit 0ec5b8b

Browse files
authored
Updated lightning demo to remove System.Drawing (sipsorcery-org#1386)
* wip: Lighnting demo adjustments for k8s deployment. * Swapped out System.Drawing and swtich to asp.net web socket server. * Removed redundant code wired back up transparency logic. * Added docker build file and instructions to readme. * Fixed docker image name.
1 parent 8723700 commit 0ec5b8b

19 files changed

Lines changed: 1138 additions & 370 deletions
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//-----------------------------------------------------------------------------
2+
// Filename: AnnotatedBitmapService.cs
3+
//
4+
// Description: Service class to annotate bitmaps. The original purpose was
5+
// to serve as the video source on a WebRTC connection.
6+
//
7+
// Author(s):
8+
// Aaron Clauson (aaron@sipsorcery.com)
9+
//
10+
// History:
11+
// 25 Feb 2025 Aaron Clauson Created, Dublin, Ireland.
12+
//
13+
// License:
14+
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
15+
//-----------------------------------------------------------------------------
16+
17+
using QRCoder;
18+
using System.Drawing.Drawing2D;
19+
using System.Drawing;
20+
using System;
21+
using Microsoft.Extensions.Logging;
22+
using System.Collections.Concurrent;
23+
24+
namespace demo;
25+
26+
public interface IAnnotatedBitmapGenerator
27+
{
28+
Bitmap? GetAnnotatedBitmap(PaidVideoFrameConfig frameConfig);
29+
}
30+
31+
public class AnnotatedBitmapService : IAnnotatedBitmapGenerator
32+
{
33+
private const float TEXT_SIZE_PERCENTAGE = 0.035f; // Height of text as a percentage of the total image height.
34+
private const float TEXT_OUTLINE_REL_THICKNESS = 0.02f; // Black text outline thickness is set as a percentage of text height in pixels.
35+
private const int TEXT_MARGIN_PIXELS = 5;
36+
private const int POINTS_PER_INCH = 72;
37+
private const int BORDER_WIDTH = 5;
38+
private const int QR_CODE_DIMENSION = 200;
39+
40+
private readonly ConcurrentDictionary<string, Lazy<Bitmap>> _qrCodeCache = new();
41+
42+
private readonly ILogger _logger;
43+
44+
public AnnotatedBitmapService(ILogger<AnnotatedBitmapService> logger)
45+
{
46+
_logger = logger;
47+
}
48+
49+
public Bitmap? GetAnnotatedBitmap(PaidVideoFrameConfig frameConfig)
50+
{
51+
try
52+
{
53+
unsafe
54+
{
55+
string imagePath = frameConfig.ImagePath;
56+
using (Bitmap baseBitmap = new Bitmap(imagePath))
57+
{
58+
var baseImage = baseBitmap.Clone() as Image;
59+
if (baseImage != null)
60+
{
61+
ApplyFilters(
62+
baseImage,
63+
DateTime.UtcNow.ToString("dd MMM yyyy HH:mm:ss:fff"),
64+
frameConfig.Title,
65+
frameConfig.BorderColour,
66+
frameConfig.Opacity,
67+
frameConfig.LightningPaymentRequest);
68+
69+
return baseImage as Bitmap;
70+
}
71+
}
72+
}
73+
}
74+
catch (Exception excp)
75+
{
76+
_logger.LogError("Exception GetAnnotatedBitmap. " + excp);
77+
}
78+
79+
return null;
80+
}
81+
82+
private void ApplyFilters(Image image, string timeStamp, string title, Color borderColor, int transparency, string? lightningPaymentRequest)
83+
{
84+
int pixelHeight = (int)(image.Height * TEXT_SIZE_PERCENTAGE);
85+
86+
using Graphics g = Graphics.FromImage(image);
87+
g.SmoothingMode = SmoothingMode.AntiAlias;
88+
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
89+
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
90+
91+
// Draw border.
92+
using (var pen = new Pen(borderColor, BORDER_WIDTH))
93+
{
94+
g.DrawLine(pen, new Point(0, 0), new Point(0, image.Height));
95+
g.DrawLine(pen, new Point(0, 0), new Point(image.Width, 0));
96+
g.DrawLine(pen, new Point(0, image.Height), new Point(image.Width, image.Height));
97+
g.DrawLine(pen, new Point(image.Width, 0), new Point(image.Width, image.Height));
98+
}
99+
100+
// Add transparency.
101+
using (Brush brush = new SolidBrush(Color.FromArgb(transparency, Color.Gray)))
102+
{
103+
g.FillRectangle(brush, new Rectangle(0, 0, image.Width, image.Height));
104+
}
105+
106+
// Add header and footer text.
107+
using (StringFormat format = new StringFormat())
108+
{
109+
format.LineAlignment = StringAlignment.Center;
110+
format.Alignment = StringAlignment.Center;
111+
112+
using (Font f = new Font("Tahoma", pixelHeight, GraphicsUnit.Pixel))
113+
{
114+
using (var gPath = new GraphicsPath())
115+
{
116+
float emSize = g.DpiY * f.Size / POINTS_PER_INCH;
117+
if (title != null)
118+
{
119+
gPath.AddString(title, f.FontFamily, (int)FontStyle.Bold, emSize, new Rectangle(0, TEXT_MARGIN_PIXELS, image.Width, pixelHeight), format);
120+
}
121+
122+
gPath.AddString(timeStamp /* + " -- " + fps.ToString("0.00") + " fps" */, f.FontFamily, (int)FontStyle.Bold, emSize, new Rectangle(0, image.Height - (pixelHeight + TEXT_MARGIN_PIXELS), image.Width, pixelHeight), format);
123+
g.FillPath(Brushes.White, gPath);
124+
g.DrawPath(new Pen(Brushes.Black, pixelHeight * TEXT_OUTLINE_REL_THICKNESS), gPath);
125+
}
126+
}
127+
}
128+
129+
if (!string.IsNullOrWhiteSpace(lightningPaymentRequest))
130+
{
131+
using var qrCode = GetCachedQRCode(lightningPaymentRequest);
132+
133+
int xCenter = (image.Width - QR_CODE_DIMENSION) / 2;
134+
int yCenter = (image.Height - QR_CODE_DIMENSION) / 2;
135+
Rectangle dstRect = new Rectangle(xCenter, yCenter, QR_CODE_DIMENSION, QR_CODE_DIMENSION);
136+
g.DrawImage(qrCode, dstRect);
137+
}
138+
else
139+
{
140+
ClearQRCodeCache();
141+
}
142+
}
143+
144+
private Bitmap GetCachedQRCode(string lightningPaymentRequest)
145+
{
146+
if (string.IsNullOrWhiteSpace(lightningPaymentRequest))
147+
{
148+
throw new ArgumentException("Payment request must be provided", nameof(lightningPaymentRequest));
149+
}
150+
151+
// Use Lazy to ensure only one QR code is generated per unique payment request.
152+
var lazyQr = _qrCodeCache.GetOrAdd(lightningPaymentRequest, key =>
153+
new Lazy<Bitmap>(() => GenerateQRCode(key))
154+
);
155+
156+
// Clone the bitmap to ensure thread safety.
157+
return (Bitmap)lazyQr.Value.Clone();
158+
}
159+
160+
private void ClearQRCodeCache()
161+
{
162+
foreach (var key in _qrCodeCache.Keys)
163+
{
164+
if (_qrCodeCache.TryRemove(key, out var lazyBitmap))
165+
{
166+
if (lazyBitmap.IsValueCreated)
167+
{
168+
lazyBitmap.Value.Dispose();
169+
}
170+
}
171+
}
172+
}
173+
174+
private Bitmap GenerateQRCode(string lightningPaymentRequest)
175+
{
176+
using QRCodeGenerator qrGenerator = new();
177+
using QRCodeData qrCodeData = qrGenerator.CreateQrCode(lightningPaymentRequest, QRCodeGenerator.ECCLevel.Q);
178+
using QRCode qrCode = new(qrCodeData);
179+
180+
Bitmap qrBitmap = qrCode.GetGraphic(20);
181+
return qrBitmap;
182+
}
183+
}

examples/WebRTCLightningExamples/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,29 @@ docker logs -f bitcoind
142142
- **Connection Issues:** Verify that the `appSettings.json`, or `appSettings.Development.json`, file has the correct LND connection details.
143143
144144
- **Payment Not Triggering Video Updates:** Ensure the payment is confirmed and the demo application is correctly processing payment events.
145+
146+
## Docker Image
147+
148+
### Build
149+
150+
`C:\dev\sipsorcery\examples\WebRTCLightningExamples> docker build -t webrtclightningdemo --progress=plain -f Dockerfile .
151+
152+
### Run
153+
154+
```
155+
set LND_URL="use real value"
156+
set LND_MACAROON_HEX="use real value"
157+
set LND_CERTIFICATE_BASE64="use real value"
158+
```
159+
160+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e Lnd__Url="%LND_URL%" -e Lnd__MacaroonHex="%LND_MACAROON_HEX%" -e Lnd__CertificateBase64="%LND_CERTIFICATE_BASE64%" webrtclightningdemo`
161+
162+
### From DokcerHub
163+
164+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e Lnd__Url="%LND_URL%" -e Lnd__MacaroonHex="%LND_MACAROON_HEX%" -e Lnd__CertificateBase64="%LND_CERTIFICATE_BASE64%" -e WAIT_FOR_ICE_GATHERING_TO_SEND_OFFER="True" -e STUN_URL="stun:stun.cloudflare.com" sipsorcery/webrtclightningdemo`
165+
166+
### Troubleshooting
167+
168+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e Lnd__Url="%LND_URL%" -e Lnd__MacaroonHex="%LND_MACAROON_HEX%" -e Lnd__CertificateBase64="%LND_CERTIFICATE_BASE64%" --entrypoint "/bin/bash" webrtclightningdemo`
169+
170+
If there is a missing font exception check the `/usr/share/fonts/truetype/msttcorefonts` directory.

0 commit comments

Comments
 (0)