Automatic Alt tags for Images

In this post we’ll be looking at 3 different file formats for images. Those are JPEG, GIF, and PNG. By default, except for the Jpeg, there is no option to add additional information about the image that is stored in the actual file. For example:

Here is the GIF details tab in properties:

And here is the PNG details tab in properties:

And here is the JPG details tab in properties. Notice the different fields.

By default, when you upload each of the images, the alt field will be empty.

So in order to fix this, we need to do 2 things.

First, we need to allow the “Title” field to be used as the “alt” tag content on the Jpeg.

Second, we need to allow the file name to be used as the “alt” tag content for the PNG and GIF files.

Generally, all images should have alt content for accessibility and SEO puproses, and in most cases, something is better than nothing. The Jpeg changes allow for the those images to have highly curated alt content while the png and gif solution offers a good starting point. Let’s get to the fix.

JPEG ALT tags in Sitecore

You’ll need to patch in a new version of the JpegMedia class that Sitecore provides. I’ve create a new version that is essentially the exact same code, except that it adds in this line: this.SetFieldValue(innerItem, “alt”, reader, 270);

This takes the value from the “Title” field and puts that in the “alt” field of the Sitecore item. I knew this because when you fill out the Image Description – Title field, Sitecore already accesses that date and puts it in the Image Description field. I just added the line of code to also use it for the “alt” field. The code is below the image.

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Drawing.Exif;
using Sitecore.Drawing.Exif.Properties;
using Sitecore.SecurityModel;
using System;
using System.Drawing;
using System.Globalization;
using Sitecore.Resources.Media;

namespace SampleSite.Foundation.Base.Resources.Media
{
    /// <summary>ImageMedia class</summary>
    public class JpegMedia : ImageMedia
    {
        /// <summary>
        /// Clones the source object. Has the same effect as creating a
        /// new instance of the type.
        /// </summary>
        /// <returns></returns>
        public override Sitecore.Resources.Media.Media Clone()
        {
            Assert.IsTrue(this.GetType() == typeof(JpegMedia), "The Clone() method must be overriden to support prototyping.");
            return (Sitecore.Resources.Media.Media)new JpegMedia();
        }

        /// <summary>Clears the meta data of a media item.</summary>
        protected override void ClearMetaData()
        {
            base.ClearMetaData();
            this.ClearFields("make", "dateTime", "imageDescription", "model", "software", "artist", "copyright");
        }

        /// <summary>Updates the meta data of a media item.</summary>
        public override void UpdateMetaData(MediaStream mediaStream)
        {
            base.UpdateMetaData(mediaStream);
            if (!mediaStream.AllowMemoryLoading)
            {
                Tracer.Error((object)"Could not update JPEG meta data as the image is larger than the maximum size allowed for memory processing. Media item: {0}", (object)mediaStream.MediaItem.Path);
            }
            else
            {
                Reader reader = (Reader)null;
                using (Image image = this.GetImage())
                {
                    if (image != null)
                        reader = Reader.Parse(image);
                    Item innerItem = this.MediaData.MediaItem.InnerItem;
                    using (new EditContext(innerItem, SecurityCheck.Disable))
                    {
                        this.SetFieldValue(innerItem, "make", reader, 271);
                        this.SetFieldValue(innerItem, "dateTime", reader, 306);
                        this.SetFieldValue(innerItem, "imageDescription", reader, 270);
                        this.SetFieldValue(innerItem, "alt", reader, 270);
                        this.SetFieldValue(innerItem, "model", reader, 272);
                        this.SetFieldValue(innerItem, "software", reader, 305);
                        this.SetFieldValue(innerItem, "artist", reader, 315);
                        this.SetFieldValue(innerItem, "copyright", reader, 33432);
                        this.SetGeoFieldValue(innerItem, "latitude", reader, 2);
                        this.SetGeoFieldValue(innerItem, "longitude", reader, 4);
                    }
                }
            }
        }

        /// <summary>Sets a field value.</summary>
        /// <param name="item">The item.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="reader">The reader.</param>
        /// <param name="tagId">The tag id.</param>
        private void SetFieldValue(Item item, string fieldName, Reader reader, int tagId)
        {
            item[fieldName] = string.Empty;
            if (reader == null)
                return;
            Property property = reader.GetProperty(tagId);
            if (property == null || !(property is StringProperty stringProperty) || stringProperty.Value == null)
                return;
            item[fieldName] = stringProperty.Value;
        }

        /// <summary>Sets a field geolocation value.</summary>
        /// <param name="item">The item.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="reader">The reader.</param>
        /// <param name="tagId">The tag id.</param>
        private void SetGeoFieldValue(Item item, string fieldName, Reader reader, int tagId)
        {
            item[fieldName] = string.Empty;
            if (reader == null)
                return;
            Property property1 = reader.GetProperty(tagId);
            if (property1 == null || !(property1 is GeoLocationProperty locationProperty))
                return;
            int num = 1;
            switch (tagId)
            {
                case 2:
                    Property property2 = reader.GetProperty(1);
                    if (property2 != null && ((StringProperty)property2).Value == "S")
                    {
                        num = -1;
                        break;
                    }
                    break;
                case 4:
                    Property property3 = reader.GetProperty(3);
                    if (property3 != null && ((StringProperty)property3).Value == "W")
                    {
                        num = -1;
                        break;
                    }
                    break;
            }
            item[fieldName] = (locationProperty.Value * (double)num).ToString((IFormatProvider)CultureInfo.InvariantCulture);
        }
    }
}

Here is the patch file for the JPEG solution.

    <mediaLibrary>
      <mediaTypes>
        <mediaType name="JPEG image" extensions="jpg, jpeg, jpe, jfif">
          <prototypes>
            <media type="SampleSite.Resources.Media.JpegMedia, SampleSite.Foundation.Base" patch:instead="*[@type='Sitecore.Resources.Media.JpegMedia, Sitecore.Kernel']"/>
          </prototypes>
        </mediaType>
      </mediaTypes>
    </mediaLibrary>

GIF and PNG Alt Text in Sitecore

This one is not so easy. We had to make a business decision to use the file name for the alt text, because as I showed earlier, there are Title or Subject fields on the properies of the GIF and PNG Files. To use the file name for these files, follow the code below.

using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetMediaCreatorOptions;

namespace SampleSite.PipelineProcessors.MediaCreatorOptions
{
    public class SetAlternateText
    {
        public void Process(GetMediaCreatorOptionsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (!string.IsNullOrWhiteSpace(args.Options.AlternateText))
            {
                return;
            }

            args.Options.AlternateText = GetAlternateText(args);
        }

        protected virtual string GetAlternateText(GetMediaCreatorOptionsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (string.IsNullOrWhiteSpace(args.Options.Destination) || args.Options.Destination.IndexOf("/") < 0)
            {
                return string.Empty;
            }

            int startofNameIndex = args.Options.Destination.LastIndexOf("/") + 1;
            return args.Options.Destination.Substring(startofNameIndex);
        }
    }
}
<getMediaCreatorOptions>
        <processor type="SampleSite.PipelineProcessors.MediaCreatorOptions.SetAlternateText, SampleSite.Foundation.Base"/>
</getMediaCreatorOptions>

Here are the results:

The GIF used the filename as the alt text.

The JPEG used the “title” field if the image description section as the alt field.

The PNG used the filename as the alt text.

About Phil Paris

Hi, my name is Phil Paris and I’m a Sitecore Architect and general Sitecore enthusiast. I’ve been working with Sitecore since 2013. Through this blog I will be sharing Sitecore insights, tips and tricks, best practices and general knowledge with the hopes to further the community at large. Please feel free to reach out to me at any time!

View all posts by Phil Paris →