Media

Media represents user content such as images and documents that were uploaded to the website to be presented on some page or is associated with a page or any other type of content.

See also

In opposite to media, static content is usually part of the website project and under the control of a developer. Please see more information about static assets within the topic for Cubane’s Resources.

Introduction to Media Content

Cubane represents media content as a Django model which contains some metadata of one particular image or document.

Generally, media is:

  • A single model instance of Media.
  • Imported from cubane.media.models.
  • Usually created and managed through Cubane’s Backend System.
  • Can be created and managed programmatically.
  • Usually, represents an image, but can also represent a document, such as a PDF file.

This rather abstract representation provides a fundamental layer in Cubane for a number of features. In particular, for images, we want to make your life working with imagery as simple as possible while allowing you to build fast and compelling websites easily.

Please feel free to browse this topic’s section in more detail. However, please allow us to give you a quick overview of what you can with Media:

  • Lazy loading of images and background images.
  • Lazy loading of images with clip paths.
  • Responsive image versions.
  • Image shapes.
  • Art Direction.
  • On-Demand image manipulation, in particular for SVG images.
  • Responsive SVG images with embedded bitmap layers.
  • Lazy loading of SVG images that are then safely embedded into the DOM for further manipulation.

Usually, when you define the structure of your site, you will make references to the Media model. Common base implementations are provided by Cubane that also defines some references. For example, a CMS page may refer to Media to represent the main image of that page.

As you build your website templates, you will use media template tags to render a media item in a particular way. For example, you may define a particular shape that the image should present itself.

Finally, your content authors may use Cubane’s backend system to insert media into page content or connect media items to other entities in the system.

Loading Media Assets

When rendering a media asset on the website through a template, we generate a bit of markup that instructs the browser to present an image or a link to a document.

In the case of an image, this is mostly straight forward:

<img src="/media/foo.jpg" alt="Foo">

In Django template code, this might look like this (myImage being an instance of Media):

<img src="{{ myImage.url }}" alt="{{ myImage.caption }}">

This - as you know - will instruct the browser to load foo.jpg and present it within the website document; so far so good.

All major media-related features in Cubane have one concept in common: Media assets are loaded when actually visible and the concrete image resolution that is loaded depends on actual properties of the device, such as pixel density and the surface area that the image will occupy.

Lazy Loading

This process, which is often referred to as lazy-loading can provide a number of benefits:

  • Images are only loaded when actually visible. In particular, when presenting a lot of images, it can speed up the loading of your website significantly.
  • The resolution of the actual image that is downloaded is determined based on the pixel density of the device and the surface area that the image will occupy. Generally speaking, this means that smaller-sized images are downloaded on devices with smaller displays, while larger images are downloaded on larger screens.

Referring back to our example from above, here is how you do it in a template using lazy-loading:

{% load media_tags %}

{% image myImage %}

The image template tag renders a bit of markup that contains some metadata about the given image myImage. This metadata is then used by the media loader at runtime to determine the actual image source that is used to render the image and how the image is actually being presented on the screen.

Without going into too many details on the media loader itself within this section, it is worth mentioning the following side effects from using the media loader:

  • By default, a lazy-loaded image will occupy the full width of its container element.
  • The space that any lazy-loaded image will occupy will be reserved so that any content that follows is not pushed down when the image is finally presented. This way, the arrangement and layout of the website are not disrupted or changed once an image has been loaded.
  • Images may load as a user scrolls the page as more images are revealed.
  • Scrollable areas that are embedded within the page need to be annotated in a certain way so that the media loader is aware that scrolling those areas may reveal further images that need to be loaded.

Media Loader

In order to take advantage of lazy loading, your website’s frontend has to embed the cubane.medialoader app, which provides the javascript-based runtime for actually loading images on your website.

First, simply include cubane.medialoader within settings.INSTALLED_APPS. Typically you would need to include cubane.media as well in order to use the media tag library and the media model Media.

INSTALLED_APPS = [
    ...
    'cubane.media',
    'cubane.medialoader',
    ...
]

In order to include the media loader’s javascript resources within your website, you would typically define a resource bucket for this purpose. Among your own CSS and javascript, you may want to include the media loader here as well.

We tend to embed the media loader inlined within the website, which speeds up the downloading of images for the tradeoff of increasing the overall size of the website’s markup:

RESOURCES = [
    ...
    'inline': [
        'cubane.medialoader'
    ],
    ...
]

In your base template, you can use the inline_resource template tag to embed all resource content for the given resource pool inline:

{% load resource_tags %}
<html>
    <body>
        ...

        {% inline_resources 'inline' 'js' %}
    </body>
</html>

You can read more about how resources are organised and managed in the Resources section.

Rendering Media Assets

The cubane.media package provides the media_tags template library, which makes a number of template tags available for rendering media items from templates.

Cubane provides a number of ways for rendering images. For example, you may want to define the shape of the image, so that the resulting image that is presented always fits a certain aspect ratio.

Or, you may want to use an image as a background image, so that you can use CSS techniques to cover an area of the website that is not pre-determined.

Usually, all those different ways of rendering an image have one principle in common: Media assets are loaded when actually visible and the concrete image resolution that is loaded depends on actual properties of the device, such as pixel density and the surface area that the image will occupy.

Plain Image

The easiest way to render an image is to simply use an img HTML tag to render the image, such as:

<img src="{{ myImage.url }}" alt="{{ myImage.caption}}">

which will simply present the image myImage with the caption as provided for the alternative text. myImage.url refers to the original image that was uploaded in its original aspect ratio.

Note

The Media model provides a number of properties, in particular for different shapes and sizes that can be used to render different image versions if required. For more information, please refer to the Media Model Reference.

In most cases, you would like to utilise lazy loading which you can do by using the media template tag library.

Lazy Loaded Image

To render the same image MyImage using lazy-loading within templates, use the image tag in the following way:

{% load media_tags %}

{% image myImage %}

This renders the same image in its original aspect ratio but is using the lazy-loading mechanism instead. Moreover, the media loader will determine at runtime which image version to use. For example, a phone may load a smaller resolution image than a high-res desktop display device might.

Tip

The term {% image myImage %} is equivalent with {% image myImage ‘original’ %}, which refers to the original shape of the image, which is always the original image that was uploaded for myImage in its original aspect ratio.

Responsive Image Sizes

For each image uploaded, Cubane will generate different versions of the same image in the same aspect ratio but with lower resolution.

This will ensure that devices with lower resolution displays will load lower resolution images for example.

You can define image sizes in your project settings, like:

IMAGE_SIZES = {
    'xx-small':  50,
    'x-small':  160,
    'small':    320,
    'medium':   640,
    'large':    900,
    'x-large': 1200
}

Cubane supports the following named sizes (from smallest to largest): xxx-small, xx-small, x-small, small, medium, large, x-large, xx-large and xxx-large.

You cannot add new versions, but you can define a subset of those image sizes based on your requirements (to use fewer image versions) and - more importantly - you can change the width of each image size (in pixels).

For example, the code above defines that there is an image size xx-small, which is 50 pixels wide. The corresponding height is determined for each image based on its individual aspect ratio.

You should adjust these numbers based on your specific requirements. Test your website and adjust as needed.

Tip

You can regenerate all images after having changed these numbers with the following management command, which is provided by the cubane.media app:

$ python manage.py create_thumbnails

This is possible because Cubane will maintain a copy of the original image that was uploaded. The create_thumbnails command will generate all required image version from the original image file.

Also, keep in mind that some devices have a higher pixel density. For example, some phones may have a device pixel ratio of two or more for example. This means that for a screen of 400 pixels width, the device actually displays 800 physical pixels.

The media loader will take this into consideration and will present the best possible match based on the sizes defined in your project settings for IMAGE_SHAPE. The media loader may upscale an image a tiny bit in order to avoid having to download a much bigger image size. However, at some point in time, the media loader will load the bigger image version and the final image will be downscaled. There is always a tradeoff.

When Cubane generates different image versions for each size, it will never upscale an image. If the original image that was uploaded is only 400 pixels wide, then Cubane will quite happily generate the sizes xx-small, x-small and small, but it will not generate any versions with higher resolutions because the result would be upscaled. The media loader will simply load the highest possible resolution that is required but also available.

Maximum Image Width

The maximum image width that Cubane supports are 2,400 pixels by default, but you can change this in your project settings:

IMG_MAX_WIDTH = 2400

Any image wider than 2,400 pixels is scaled down to 2,400 pixels and then stored permanently as the original version of the image. From there, any additional image versions are generated by downscaling the image from the original version.

This process exists in order to keep uploaded media assets to a certain maximum size and preventing storing original images that are too large. Also, the processing time for resizing and generating different image version based on the original image is greatly improved if the original image size is reduced.

Note

You can turn off this behaviour by setting IMG_MAX_WIDTH to None.

Image Shapes

Cubane will automatically generate different shapes for each image, as defined in your project settings, for example:

IMAGE_SHAPES = {
    'header': '1150:285',
    'listing': '300:300'
}

A shape is basically a specific aspect ratio in which you want the image to appear. Cubane will crop the image so that the image content itself is not distorted.

Remember, the actual width of an image depends on physical device properties, therefore IMAGE_SHAPES defines a list of named shapes and maps a specific aspect ratio to each of them. For example, the shape named listing may have been defined in different ways, all of which are equivalent, because they all express the same aspect ratio 1:1:

IMAGE_SHAPES = {
    'listing1': '1:1',
    'listing2': '300:300',
    'listing3': '1024:1024'
}

This is simply a way to express the form of the shape (in the case of listing a square).

When rendering an image by using the image tag, the second argument may specify the name of the shape to use. The shape original always refers to the original aspect ratio of the image that was uploaded.

{% load media_tags %}

<!-- render image in original aspect ratio -->
{% image myImage %}
{% image myImage 'original' %}

<!-- render image in the header shape -->
{% image myImage 'header' %}

<!-- render image in the listing -->
{% image myImage 'listing' %}

<!-- error: unknown shape -->
{% image myImage 'does_not_exists' %}

The shape must be declared ahead of time in IMAGE_SHAPES. If the image tag cannot find a shape, Cubane will render an error in DEBUG mode. In production mode, this error will be ignored.

Note

Cubane will generate a number of different responsive versions for each image and each shape. For example, if you had six responsive image versions and two shapes, then Cubane would generate twelve different individual assets for each image uploaded.

If you can, keep the number of versions and shapes low.

Image Shape Names

Cubane’s backend system provides an overview of all shapes that are declared in the system. This is particularly useful when uploading new images and changing the focal point of an image.

The focal point of an image will influence how Cubane will crop an image if necessary to fit the image into specific shapes.

For example, a landscape image showing a person’s face might need to be cropped to fit a more square or portrait format. If the focal point of the image is the centre of the face, then all cropped shapes that are generated would have the face in it and the system would not cut off the face.

To clearly identify the different shapes that have been declared, a more useful human-readable name may be declared for each shape:

IMAGE_SHAPE_NAMES = {
    'header': 'Header Background Image',
    'product': 'Main Product Shot (Listing and Details Pages)',
    ...
}

A more detailed description of each shape will help users to identify in which way each image shape is used on the website.

Image Shape CSS

Cubane’s lazy-loading mechanism uses a specific CSS technique to ensure that the image area is reserved on the website so that when the image is finally loaded, it will not disrupt the layout of the page by pushing content down.

When rendering an image using the image tag, the system will generate markup similar to the example below:

{% load media_tags %}

{% image myImage 'header' %}

will render markup like:

<span class="lazy-load">
    <span class="lazy-load-shape-header">
        <noscript data-ar="100.0" data-path="/0/1/my-image.jpg" data-shape="header" data-sizes="small|medium|large" data-title="My Image">
            <img src="/media/shapes/original/large/0/1/my-image.jpg" alt="My Image">
        </noscript>
    </span>
</span>

If no javascript is executed, then this solution will still be able to present an image on the screen due to the <noscript> tag in the markup.

More importantly, the outer container with the CSS class lazy-load-shape-header enforces a specific aspect ratio by applying the following CSS rules:

.lazy-load {
    display: block;
}

.lazy-load > span {
    display: block;
    position: relative;
    height: 0;
    overflow: hidden;
}

.lazy-load-shape-header {
    padding-bottom: 100%;
}

Because the outer container has zero height, the padding-bottom CSS rule will effectively apply a fixed aspect ratio of 1:1 to the container. Therefore the image within will load just fine without having any effect on the layout of the outside of its container. Moreover, the image will naturally fit the container’s aspect ratio because the media loader will obviously load an image version that matches the aspect ratio of the container perfectly.

Let us assume that the header shape had an aspect ratio of two to one, then the system would generate the following CSS rule for the header shape instead:

.lazy-load-shape-header {
    padding-bottom: 50%;
}

When embedding all resources for the cubane.medialoader app into your website, you will not only include the javascript code library for loading images. You will also include some CSS code along with it.

Some of this CSS code is related to the lazy-load container itself but it also contains all CSS rules related to the shape’s aspect ratio which are generated based on all shapes declared in your application settings.

When rendering images using the cubane.media template library, this will take advantage of such CSS rules as described above.

Tip

You can use those shape-related CSS rules by yourself in a different context, perhaps you would like to render a background image that follows a certain aspect ratio or you have another element that should always have the same aspect ratio as one of your image shapes.

Feel free to apply the lazy-load-shape-header CSS class to any element in your markup for your own purposes. When you do, do not apply the lazy-load class since this will invoke the media loader to attempt to load an image unless this is precisely your intention.

Background Images

Sometimes it is useful to represent an image or a set of images as a background image. By doing so, we have a lot more flexibility; for example, we could use different CSS techniques to present the image within a region of the website that does not have a fixed shape.

For instance, let’s assume that we have a header image on our website, which always takes 100% of the width but has a fixed height. When resizing the browser window, we will obviously end up with a different image shape every time: While the width is changing, the height remains fixed to a certain value.

The following CSS rule should stretch the background image so that the entire area of the element is covered by the image.

.header-image {
    background-size: cover;
}

Here is the corresponding markup for rendering the background image with lazy-loading:

<div class="header-image lazy-load" {% background_image myImage 'original' %}></div>

The background_image template tag will render additional data attributes for the header-image element. You are free to use any other element, it does not necessarily have to be a div element.

However, it is important that you use the CSS class lazy-load, otherwise, the media loader will not be able to identify this element as a lazy-loading container.

The media loader will then use those additional data attributes in order to determine the actual image version that will be loaded whenever the image becomes visible.

By default, the actual image version that is loaded depends on the width of the element - which is the div element in our example from above. In some situations, you may want to take the height of the element into consideration as well. For example, you may have an element that expands vertically depending on other content on the same page next to it. If the height is not taken into consideration, then the resulting image may appear pixelated because the image resolution has been determined based on the width alone - ignoring the height of the element (assuming we are still using the cover CSS rule for stretching the background image from above).

For lazy-loaded background images, you can specify that the height should also be taken into consideration in the following way:

<div class="header-image lazy-load" {% background_image myImage 'original' True %}></div>

The True argument specifies that the height of the element is taken into consideration as well, therefore the media loader may load a higher resolution as it would do otherwise if the height dictates a higher resolution image.

Note

Depending on the height, you may end up, loading a very large image in order to satisfy the height requirement and a lot of the image is cropped as a consequence.

Alternatively, you can also use a technique called Art Direction, which allows you to change the shape of a lazy-loaded image based on device properties.

Size Templates

Lazy-loading images will work in most circumstances; however, there are a few instances where this will not work automatically and further markup or JavaScript code is needed.

A typical carousel implementation is a good example where the dimensions of all images might not be known ahead of time. Since most slides are invisible by default (assuming that only one slide of the carousel is active at any given time), all images within inactive slides will not be loaded automatically.

Those images are either hidden or are located off-screen due to the nature of the carousel.

By default, the lazy loader will take the direct container of the image into consideration when determining visibility and size of the image. For background images, this might be the element itself and not its direct parent element.

You can annotate the direct parent that contains the image (or the element containing a background image) with the attribute data-size. In this case, the lazy loader will determine the size of the corresponding image based on a common parent element higher up the hierarchy that has been annotated with the attribute data-size-template.

Consider the following example:

{% load media_tags %}

<div class="carousel-container" data-size-template>
    <div class="carousel-slides">
        {% for slide in slides %}
            <div class="carousel-slide" data-size>
                {% image slide.image %}
            </div>
        {% endfor %}
    </div>
</div>

Each image would normally collapse to zero width or would be located off-screen or otherwise invisible due to the CSS style for the carousel. However, because the direct container element with the CSS class carousel-item has the data-size attribute, the lazy loader will scan upwards to locate the containing element with the attribute data-size-template. Then this element (with the CSS class carousel-container) will be used instead to determine the actual size of the image and its visibility.

Note

When working with background images, the data-size attribute must be applied to the element itself and not to its parent like the following example demonstrates:

{% load media_tags %}

<div class="carousel-container" data-size-template>
    <div class="carousel-slides">
        {% for slide in slides %}
            <div class="carousel-slide">
                 <div data-size class="carousel-slide-image lazy-load" {% background_image myImage 'original' %}></div>
            </div>
        {% endfor %}
    </div>
</div>

Art Direction

Many websites feature a bold and big header image, which is great on desktop devices, but if you scale the same shape down to the size of a phone, then the narrow landscape shape might not be exactly what you wanted.

Let’s take the following example:

{% load media_tags %}

<div class="header">
    {% image headerImage 'header' %}
</div>

We assume that the header shape has been defined as a narrow landscape format, something like:

IMAGE_SHAPES = {
    'header': '1200:400'
}

When scaling the website down to the size of a phone, the image within the header of the website will scale down proportional to its shape. Let’s assume that the screen width of our phone is 400 pixels, then the narrow header image will have been scaled itself down to just 133 pixels.

This is all fine, but the point is: We may want to change the shape of the image based on the device. This is what Art Direction can provide.

Let’s assume that we wanted to change the shape from landscape to a square on phones. We first invent a new shape for this:

IMAGE_SHAPES = {
    'landscape': '1200:400',
    'square': '400:400'
}

We removed the shape header and replaced it with landscape. We also added a new shape called square to represent our square shape for phones. We can now define an art-directed shape called header, which will determine the actual shape that shall be used based on the screen width of the website, like the following:

IMAGE_ART_DIRECTION = {
    'header': {
        '767': 'square',
        '*':   'landscape'
    }
}

This declares a new art-directed shape called header as we had before, but now the shape is art-directed: This means that the actual shape is determined by the list of rules, we declared as above.

The left-hand side declares the maximum screen width (in logical pixels, inclusive), which is then mapped to the name of a regular image shape that shall be used in that case. The declaration * is used in the case that no other rule matches.

Finally, we can simply render an image by using the art-directed shape instead of referring to a concrete shape:

{% load media_tags %}

<div class="header">
    {% image headerImage 'header' %}
</div>

Now the shape header has a different meaning. Cubane will resolve the actual shape that is used at runtime based on the width of the browser window.

Art Direction and CSS Classes

Cubane will generate CSS classes for each shape declared in your application settings as described in section Image Shape CSS. All image shapes that have been declared for this example would result in the following CSS code to be generated:

.lazy-load-shape-square {
    padding-bottom: 100%;
}

.lazy-load-shape-landscape {
    padding-bottom: 33.3333%;
}

In order to accommodate art direction, additional CSS classes are generated for each art-directed shape using CSS media queries based on the art-direction rules that are declared in your application settings, such as:

@media(max-width: 767px) {
    .lazy-load-shape-header {
        padding-bottom: 100%
    }
}

@media(min-width: 768px) {
    .lazy-load-shape-header {
        padding-bottom: 33.3333%
    }
}

Feel free to use those CSS rules for your own element if you wanted to.

SVG Vector Images

Cubane’s media pipeline has full support for SVG vector images. You can upload SVG files by using the backend system as you could upload any other type of image file. However, the processing of vector images is a bit different to bitmap based image formats, such as jpg or png, although the way you use SVG based images is exactly the same:

{% load media_tags %}

{% image mySvgImage 'header' %}

This will render the SVG image mySVGImage in the aspect ratio for the shape header as the system would render any other (bitmap-based) image.

The system will generate different image versions for each shape that has been declared in the application settings. SVG images are cropped by adjusting their viewBox property according to the shape’s aspect ratio.

Regarding responsive images, because a vector is already scalable, we do not necessarily need to generate different image versions for different screen sizes, or do we?

As it turns out, you can quite happily embed bitmap data within an SVG file, which gives you effectively a combined image with scalable vector components as well as one or more layers of bitmap data mixed together.

Given that most SVG images will probably contain vector graphics without any bitmap information, this use case is still extremely useful: Think about an eCommerce example, where we sell clothing. You could easily see how the clothing itself might be vector shapes, with a bitmap-based shadow map on top of it. This way it will give the otherwise solid and plain looking garment depth and definition and will appear dimensional and not flat.

Representing clothing like this has another benefit: We might want to customise colours for individual layers of the garment (e.g. colouring different parts of the SVG image programmatically). SVG gives us this flexibility as we will see shortly.

Because SVG images may contain bitmap data, Cubane’s media system will generate different image versions for each SVG image uploaded. The system will analyse the SVG markup and generates a version where any embedded bitmap data has been scaled down to fit the maximum width of the image version.

In order to be consistent, the system will do this process for any SVG image, regardless of whether the SVG image contains bitmap data or not.

Media URLs

By default, media data is stored within the media folder and your web server is typically configured to serve media assets directly without invoking python.

This is a common setup when hosting Django-based applications.

For example, you can access any original media image by using the following URL:

/media/originals/<bucket>/<primary-key>/<filename>.<ext>

For example:

/media/originals/0/1/my-image.svg

The first digit 0 represents the bucket or group of images. Cubane will organise images in groups of 1,000 images per folder. The second digit 1 represents the numeric primary key of the corresponding database record.

Any image with a primary key between 0 and 999 will be associated with the bucket identifier of 0, any primary key between 1,000 and 1,999 with 1 and so forth.

The last component is a representation of the image caption. Whenever you change the caption of an image, all image files will be renamed to reflect the full caption of the image within its filename. Because each image is organised within a folder that represents a unique primary key of the underlying database record, the caption may contain duplicates.

Any derived image version is organised by applying the following structure:

/media/shapes/<shape>/<size>/<bucket>/<primary-key>/<filename>.<ext>

For example:

/media/shapes/header/small/0/1/my-image.svg

The prefix /media/ can be changed in your application settings, such as:

MEDIA_URL = 'media-assets'

Which will then result in the following paths:

/media-assets/originals/0/1/my-image.svg
/media-assets/shapes/header/small/0/1/my-image.svg

Note

When changing the MEDIA_URL settings variable, the system will not rename the underlying physical path location. If you already uploaded some media files, you would need to rename the physical folder on the filesystem by yourself or adjust the configuration of settings.MEDIA_ROOT accordingly.

In most cases you will not need to work with the underlying folder structure directly: Cubane’s Media model provides a set of helper methods and properties that generate all the different URLs and file paths for you. Please refer to the Media Model Reference for more information.

Media API URLs

In Production mode, you would usually set up a web server to serve media files directly without invoking python, Django or Cubane to do so. However, there are some cases where you would like to apply modifications to an image on demand.

Cubane will generate all image versions ahead of time for best performance in production, so there is no need to resize or crop images on demand.

However, in particular, for SVG images you may want to colourise different shapes of an SVG image at runtime based on user’s choice. Coming back to the eCommerce example from earlier, a customer may configure a garment by choosing different colours for different parts of the garment.

If you need to render a specific version based on those colour choices, then Cubane’s media API is the way to go.

Usually, you can access any image by using the following URLs, as explained in the Media URLs. section:

/media/shapes/header/small/0/1/my-image.svg

If you need to customise an SVG image at runtime, you can simply use the media API URL instead:

/media-api/shapes/header/small/0/1/my-image.svg

The URL is exactly the same as the usual image URL, only the prefix changed from /media/ to /media-api/. Any image served via the media API URL will generate a media file on demand and will invoke python, Django and Cubane to do so.

Note

Because an image is generated on demand, this process is significantly slower than using pre-generated image versions.

The benefit of generating the image on demand is that additional transformation functions can be applied at runtime specific to this request. For example, the following image changes the SVG shape with the identifier base to red:

/media-api/shapes/header/small/0/1/my-image.svg?base=red

The general rule for applying colorisation attributes like this is:

<identifier>=<attribute>:<value>

The attribute can be omitted, in which case the default attribute is fill, which is the instruction for an SVG shape’s fill colour. These two rules are therefore equivalent:

/media-api/shapes/header/small/0/1/my-image.svg?base=red
/media-api/shapes/header/small/0/1/my-image.svg?base=fill:red

Other attributes, such as stroke-width or stroke are also supported in the context of SVG images:

/media-api/shapes/header/small/0/1/my-image.svg?base=stroke:blue

Multiple attributes can be combined for the same or different shapes:

/media-api/shapes/header/small/0/1/my-image.svg?base=fill:red&base=stroke:blue&base=stroke-width:2&zip=green

When using colours, you can use named colours such as red, green and blue, but you can also use any RGB colour in its hexadecimal representation:

/media-api/shapes/header/small/0/1/my-image.svg?base=e9d8c7

Note

In CSS for example, RGB colours are usually represented by a leading sharp symbol, such as #e9d8c7. Since # has a reserved meaning within a URL and would need to be escaped, the media API simply omits the sharp symbol altogether.

If you start out with a Media instance, then you can simply use image template tag to generate markup that will load the image lazily through the media api with the given customisations applied.

{% load media_tags %}

{% image mySvgImage 'header' 'base=fill:red&base=stroke:blue' %}

If all you need is the URL itself - for example within an email template where lazy loading images are not available - then you can use the following template tag instead:

{% load media_tags %}

{% media_api_url mySvgImage 'large' 'base=fill:red&base=stroke:blue' %}

The cubane.svgicons app extends the Media API by allowing SVG icons sets to be downloaded in Debug mode only. Cubane’s SVG icon system relies on this extension in order to serve external SVG icon files in Debug mode.

In Debug mode, any SVG icon set file can be accessed by using the following Media API URL (this file is usually only available in Production mode and is generated as part of the deployment process of the website):

/media-api/svgicons/frontend.svg

This URL refers to the SVG icon set file containing all SVG icons that have been declared for the frontend bucket. The SVG icon file is generated on demand.

Individual SVG icons can be accessed in the following way:

/media-api/svgicons/frontend/phone.svg

This URL refers to an SVG icon file which only contains the phone resource as declared for the frontend resource bucket.

Embedding SVG Images

All rendering methods we’ve discussed so far are referring to an image by using the img tag. Even when loading the image lazily, the actual markup ends up to be an img tag.

Under specific circumstances, in particular with SVG images, you may want to embed the actual image data within the document itself. For SVG images, this could mean that you like to change individual parts of the SVG images programmatically using javascript for example.

In order to do this consistently, the best possible option is to embed the actual SVG document within the HTML markup itself, which you can do in the following way:

{% load media_tags %}

{% inline_image mySvgImage 'header' %}

Note

This will still load and embed the SVG images lazily and only if visible, even though the image will be embedded into the DOM of the HTML document.

When embedding SVG images, the markup of the SVG document becomes part of the DOM of the website document. SVG images may contain id attributes and <style> tags. Usually, this would mean that id attributes may collide because of duplication, since an identifier may collide with any other identifier on your website. Perhaps even more severe, style information that was previously embedded within the SVG image is now applied globally to the entire document and may affect other parts of your website.

To avoid these problems, all SVG images are optimised in the following way:

  • Any style declarations within SVG images are removed and embedded as inline style attributes.
  • All identifiers are prefixed with a short term. The prefix is unique to the SVG image file, the particular responsive version and shape. This means that different SVG images and different image sizes or shapes can safely be embedded without identifiers colliding. The prefix itself is added to the svg tag as the data-prefix attribute so that the prefix can be obtained programmatically.

SVG-based clip paths

Wouldn’t it be great to break out of those rectangular shapes for images? With SVG-based clip paths, you can define an arbitrary shape, where an image - which would otherwise be rectangular - can be presented differently; for example as an ellipse or with a slope, in the shape of an arrow or any other shape, you can think of.

There is a CSS solution: The clip-path property allows us to define a specific region of an element to display, rather than showing the complete area. However, browser support is great but still limited.

The next best thing that comes to mind is SVG clip paths: Very similar in concept but — as it turns out — much wider supported in today’s browsers.

We are still going to use the clip-path CSS property, but instead of defining the clip path shape in CSS and having it acting on any arbitrary DOM element, we will use SVG instead. Both, the clip path itself and the targeted image need to live in SVG land in order to make this solution as widely supported as possible.

The clip path itself can be defined in your template like the following example:

<svg id="svg-defs" width="0" height="0">
    <defs>
        <clipPath id="my-clip-path" clipPathUnits="objectBoundingBox">
            <path d="M1,0L1,0.865C0.867,0.949 0.688,1 0.491,1C0.303,1 0.131,0.953 0,0.876L0,0L1,0Z"/>
        </clipPath>
    </defs>
</svg>

The container does not have any dimensions (0x0 in size) and can be defined anywhere within the body of the document. We define the clip path via the clipPath tag. The clip path itself is made up by any SVG shapes, for example a path.

Please note that we use objectBoundingBox for the clipPathUnits attribute. This way the path can be defined in percentage values (0.0 to 1.0) based on the bounding box of the object the clip path is assigned to. This is great for responsive images because the clip path will simply resize itself according to the image.

Once a clipping path is defined anywhere within your template, you can start using the clip path by loading an image and applying the clip path to it:

{% load media_tags %}

{% svg_image myImage 'header' 'my-clip-path' %}