Set page color based on an img in Umbraco
Published: 2021-09-03 19:55:59

# The problem

Currently working on a new website that shows a product catalogue. Part of the design is that each page has a background color that is based on the main product image. Here is how I've solved it.

## The code

We will calculate the avg color of an image when it is added to one of the product page nodes - so in the content saving event:

~/Events/ContentServiceEvents.cs

```csharp
using System;
using System.Drawing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services.Implement;
using Umbraco.Web;

namespace Test.Events {
    public class ContentServiceEvents : IComponent {
        private readonly IUmbracoContextFactory _umbracoContextFactory;

        public ContentServiceEvents(IUmbracoContextFactory umbracoContextFactory) {
            _umbracoContextFactory = umbracoContextFactory;
        }

        public void Initialize() {
            ContentService.Saving += ContentService_Saving;
        }

        public void Terminate() {
            ContentService.Saving -= ContentService_Saving;
        }

        private void ContentService_Saving(Umbraco.Core.Services.IContentService sender, Umbraco.Core.Events.ContentSavingEventArgs e) {
            // When content is saved, we loop through each saved node
            foreach (IContent node in e.SavedEntities) {

                // We only continue if it's a product page
                if (node.ContentType.Alias == "productPage") {

                    // We only continue if the saved node has the product image set
                    if (node.GetValue("productImage") != null) {

                        // We only continue if the color isn't already set
                        if (string.IsNullOrWhiteSpace(node.GetValue<string>("imageBgColor"))) {
                            using (var context = _umbracoContextFactory.EnsureUmbracoContext()) {

                                // MediaPicker v2 saves as a media udi, so we get it to grab the media object
                                var udi = node.GetValue<Udi>("productImage");
                                var img = context.UmbracoContext.Media.GetById(udi);

                                // From https://stackoverflow.com/a/20757431/16493772
                                // This is for external media storage like Azure blob storage or AWS S3 buckets
                                // We download the image based on the media url to create a bitmap we use to calculate the avg color
                                System.Net.WebRequest request = System.Net.WebRequest.Create(img.Url(mode: UrlMode.Absolute));
                                System.Net.WebResponse response = request.GetResponse();
                                System.IO.Stream responseStream = response.GetResponseStream();
                                Bitmap bm = new Bitmap(responseStream);
                                var color = AverageColor(bm);

                                // Finally we set the avg color in a property - since this is a content saving event
                                // we don't need to save exeplicitly, it'll do that at the end
                                node.SetValue("imageBgColor", color);
                            }
                        }
                    }                                
                }
            }
        }

        // From https://stackoverflow.com/a/2546080/16493772 - Edited to get around a bug where the values would overflow the max Int32 value and become negative,
        // changed ints to longs then converting back to int in the end.
        // In short - it goes through every pixel of the img, finds its RGBA value, add them all together and divide by the number of pixels to find the average color of the image.
        public string AverageColor(Bitmap bmp) {            
            long width = bmp.Width;
            long height = bmp.Height;
            long red = 0;
            long green = 0;
            long blue = 0;
            long alpha = 0;
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++) {
                    var pixel = bmp.GetPixel(x, y);
                    red += pixel.R;
                    green += pixel.G;
                    blue += pixel.B;
                    alpha += pixel.A;
                }

            Func<long, long> avg = c => c / (width * height);

            red = avg(red);
            green = avg(green);
            blue = avg(blue);
            alpha = avg(alpha);

            var color = Color.FromArgb(Convert.ToInt32(alpha), Convert.ToInt32(red), Convert.ToInt32(green), Convert.ToInt32(blue));

            var hexColor = "#" + color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2");

            return hexColor;
        }
    }
}
```

~/Composers/SiteComposer.cs

```csharp
using Test.Events;
using Umbraco.Core;
using Umbraco.Core.Composing;

namespace Test.Composers {

    [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class SiteComposer : IUserComposer {
        
        public void Compose(Composition composition) {            
            composition.Components().Append<ContentServiceEvents>();
        }
    }
}
```

Let me know if you found this interesting, or if you know of any improvements I could make!

Checking each pixel of the image is not super performant, so there may be better ways to get the color. For 8mb images this can take ~10 seconds on the first save on my current site.