24 Jul 2019
I recently launched a new website which I built in Umbraco called moneymatchpool.com
This website gathers all of the pool money matches that have been streamed on Facebook and YouTube and makes it easy for you to be able to search by player, venue and stream company.
For this site to be a success, the posts need to be shared on Facebook as that is where my target audience is.
When posts are shared on Facebook, they are far more interesting and attractive to the end user if they have an image with them.
I want it to show the 2 player profile images, the moneymatchpool.com logo and the logo of the video streaming company.
I needed to create a media handler which will generate this image dynamically based on the picked players and picked stream company.
This post shows you how I created a handler and used ImageProcessor, by James Jackson-South, to create the image. Well, ImageFactory to be precise.
This is what the header image looks like.
I added a folder called handlers and in that I added a new handler item called HeaderImage.ashx
In the web.config file I added the header image path to the umbracoReservedPaths app setting:
<add key="umbracoReservedPaths" value="~/umbraco,~/install/,~/HeaderImage.ashx" />
In the handlers section of the web.config file I added the handler:
<handlers accessPolicy="Read, Write, Script, Execute"> <remove name="HeaderImage" /> <add name="HeaderImage" verb="GET" path="HeaderImage.ashx" type="MMP.Web.handlers.HeaderImage, MMP.Web" /> </handlers>
I created an api controller for getting the profile and stream company images
using System.Collections.Generic; using Umbraco.Web.WebApi; namespace MMP.Core.Controllers.Api { public class MediaApiController : UmbracoApiController { public List<string> GetMediaPathsByMatchId(int id) { List<string> mediaPaths = new List<string>(); var match = Umbraco.TypedContent(id); var typedMatch = (MMP.Core.Models.Match)match; var homePlayer = typedMatch.HomePlayer; var awayPlayer = typedMatch.AwayPlayer; var stream = typedMatch.Stream; var typedHomePlayer = (MMP.Core.Models.Profile) homePlayer; var typedAwayPlayer = (MMP.Core.Models.Profile) awayPlayer; var typedStream = (MMP.Core.Models.Stream) stream; var homePlayerImageUrl = typedHomePlayer.ProfileImage?.Url ?? "/media/1035/silhouette.jpg"; var awayPlayerImageUrl = typedAwayPlayer.ProfileImage?.Url ?? "/media/1035/silhouette.jpg"; var streamImageUrl = typedStream != null ? typedStream.ProfileImage.Url : ""; mediaPaths.Add(homePlayerImageUrl); mediaPaths.Add(awayPlayerImageUrl); mediaPaths.Add(streamImageUrl); return mediaPaths; } public string GetMediaPathById(int id) { var mediaItem = Umbraco.TypedMedia(id); return mediaItem.Url; } } }
Ok first of all, I should say that normally you would have the actual logic in a separate library, but this is a personal project and I thought it was easier to leave it in the hanlder.
This code just returns a fallback image by loading it into ImageFactory and writing it out to the stream.
using ImageProcessor; using ImageProcessor.Imaging.Formats; using System.IO; using System.Web; namespace MMP.Web.handlers { public class HeaderImage : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/png"; GenerateFallbackImage(context); } private static void GenerateFallbackImage(HttpContext context) { var fallbackImageUrl = "~/media/moneymatchpool-logo.png"; var fallbackImagePath = context.Server.MapPath(fallbackImageUrl); byte[] photoBytes = File.ReadAllBytes(fallbackImagePath); ISupportedImageFormat format = new PngFormat() { Quality = 70 }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Save(outStream); } outStream.WriteTo(context.Response.OutputStream); } } } public bool IsReusable { get { return false; } } } }
I wanted to load images as layers on top of a background image, so I created this method to load an image as an ImageLayer
private static ImageLayer GetImageLayer(string imagePath, int quality, Size size, bool isCircle) { var imageLayer = new ImageLayer(); int rounding = 0; if (isCircle) { rounding = size.Height / 2; } byte[] photoBytes = File.ReadAllBytes(imagePath); ISupportedImageFormat format = new PngFormat() { Quality = quality }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { if (isCircle) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Resize(size) .RoundedCorners(rounding) .Save(outStream); } } else { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Resize(size) .Save(outStream); } } imageLayer.Image = Image.FromStream(outStream, true); } } return imageLayer; }
I then created the code for generating the custom image with all of the layers loaded onto it.
private static void GenerateCustomImage(HttpContext context) { string matchId = context.Request.QueryString["id"]; var domainAddress = context.Request.Url.GetLeftPart(UriPartial.Authority); var backgroundImagePath = context.Server.MapPath("~/Media/background.png"); var playerOneUrl = ""; var playerTwoUrl = ""; var streamUrl = ""; List<string> playerUrls; using (var httpClient = new HttpClient()) { var response = httpClient.GetStringAsync(domainAddress + "/Umbraco/Api/MediaApi/GetMediaPathsByMatchId/?id=" + matchId); playerUrls = JsonConvert.DeserializeObject<List<string>>(response.Result); } playerOneUrl = playerUrls[0]; playerTwoUrl = playerUrls[1]; if (playerUrls.Count > 2) { streamUrl = playerUrls[2]; } var playerOneImagePath = context.Server.MapPath(playerOneUrl); var playerTwoImagePath = context.Server.MapPath(playerTwoUrl); var streamImagePath = !string.IsNullOrWhiteSpace(streamUrl) ? context.Server.MapPath(streamUrl) : ""; var playerOne = GetImageLayer(playerOneImagePath, 70, new Size(400, 400), false); playerOne.Position = new Point(260, 340); var playerTwo = GetImageLayer(playerTwoImagePath, 70, new Size(400, 400), false); playerTwo.Position = new Point(1260, 340); ImageLayer stream = null; if (!string.IsNullOrWhiteSpace(streamImagePath)) { stream = GetImageLayer(streamImagePath, 70, new Size(300, 300), true); stream.Position = new Point(810, 240); } byte[] photoBytes = File.ReadAllBytes(backgroundImagePath); ISupportedImageFormat format = new PngFormat() { Quality = 70 }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Overlay(playerOne) .Overlay(playerTwo) .Overlay(stream) .Save(outStream); } outStream.WriteTo(context.Response.OutputStream); } } }
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/png"; string matchId = context.Request.QueryString["id"]; if (string.IsNullOrWhiteSpace(matchId)) { GenerateFallbackImage(context); } else { try { GenerateCustomImage(context); } catch (Exception ex) { var error = ex; GenerateFallbackImage(context); } } }
using ImageProcessor; using ImageProcessor.Imaging; using ImageProcessor.Imaging.Formats; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Net.Http; using System.Web; namespace MMP.Web.handlers { public class HeaderImage : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/png"; string matchId = context.Request.QueryString["id"]; if (string.IsNullOrWhiteSpace(matchId)) { GenerateFallbackImage(context); } else { try { GenerateCustomImage(context); } catch (Exception ex) { var error = ex; GenerateFallbackImage(context); } } } private static void GenerateFallbackImage(HttpContext context) { var fallbackImageUrl = "~/media/moneymatchpool-logo.png"; var fallbackImagePath = context.Server.MapPath(fallbackImageUrl); byte[] photoBytes = File.ReadAllBytes(fallbackImagePath); ISupportedImageFormat format = new PngFormat() { Quality = 70 }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Save(outStream); } outStream.WriteTo(context.Response.OutputStream); } } } private static void GenerateCustomImage(HttpContext context) { string matchId = context.Request.QueryString["id"]; var domainAddress = context.Request.Url.GetLeftPart(UriPartial.Authority); var backgroundImagePath = context.Server.MapPath("~/Media/background.png"); var playerOneUrl = ""; var playerTwoUrl = ""; var streamUrl = ""; List<string> playerUrls; using (var httpClient = new HttpClient()) { var response = httpClient.GetStringAsync(domainAddress + "/Umbraco/Api/MediaApi/GetMediaPathsByMatchId/?id=" + matchId); playerUrls = JsonConvert.DeserializeObject<List<string>>(response.Result); } playerOneUrl = playerUrls[0]; playerTwoUrl = playerUrls[1]; if (playerUrls.Count > 2) { streamUrl = playerUrls[2]; } var playerOneImagePath = context.Server.MapPath(playerOneUrl); var playerTwoImagePath = context.Server.MapPath(playerTwoUrl); var streamImagePath = !string.IsNullOrWhiteSpace(streamUrl) ? context.Server.MapPath(streamUrl) : ""; var playerOne = GetImageLayer(playerOneImagePath, 70, new Size(400, 400), false); playerOne.Position = new Point(260, 340); var playerTwo = GetImageLayer(playerTwoImagePath, 70, new Size(400, 400), false); playerTwo.Position = new Point(1260, 340); ImageLayer stream = null; if (!string.IsNullOrWhiteSpace(streamImagePath)) { stream = GetImageLayer(streamImagePath, 70, new Size(300, 300), true); stream.Position = new Point(810, 240); } byte[] photoBytes = File.ReadAllBytes(backgroundImagePath); ISupportedImageFormat format = new PngFormat() { Quality = 70 }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Overlay(playerOne) .Overlay(playerTwo) .Overlay(stream) .Save(outStream); } outStream.WriteTo(context.Response.OutputStream); } } } private static ImageLayer GetImageLayer(string imagePath, int quality, Size size, bool isCircle) { var imageLayer = new ImageLayer(); int rounding = 0; if (isCircle) { rounding = size.Height / 2; } byte[] photoBytes = File.ReadAllBytes(imagePath); ISupportedImageFormat format = new PngFormat() { Quality = quality }; using (MemoryStream inStream = new MemoryStream(photoBytes)) { using (MemoryStream outStream = new MemoryStream()) { if (isCircle) { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Resize(size) .RoundedCorners(rounding) .Save(outStream); } } else { using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true)) { imageFactory.Load(inStream) .Format(format) .Resize(size) .Save(outStream); } } imageLayer.Image = Image.FromStream(outStream, true); } } return imageLayer; } public bool IsReusable { get { return false; } } } }