Knowledge Base

Cropping with PXL

Last Modified:
27 Aug 2024
User Level:
Administrator

Prerequisites

Overview

A script added to a content layout on Media System Content Type performs the cropping. It calculates the coordinates required to crop the new image from the original image and the PXL filter used. 

The script can crop from 3 points:

  • top - crop from the top if cropping vertically or crop from the left if cropping horizontally
  • middle - crop from the middle of the image
  • bottom - crop from the bottom if cropping vertically or crop from the right if cropping horizontally

The script will work out whether the original image needs to be cropped horizontally or vertically by comparing the aspect ratio of the original image to the aspect ratio of the new size required:

  • if the aspect ratio of the original image is greater than the new size required it will crop from the top, middle or bottom
  • else the aspect ratio of the original image is less than the new size required it will crop from the left, middle or right

In this example, we want to crop a landscape image, the screenshot shows how the script would crop images with a lower and higher aspect ratio.

Screenshots showing example of how different images would be cropped to make a landscape image

In this example, we want to crop a portrait image, the screenshot shows how the script would crop images with a lower and higher aspect ratio.

Screenshots showing example of how different images would be cropped to make a portrait image

How To Install The PXL Cropping Script

  1. Add a Content Layout to the Media System Content Type
    Element Value
    Name image/pxl-crop
    File extension Default
    Syntax type JavaScript
    Content layout processor JavaScript Content
  2. Paste in the full script:
    try {
        var FullListOutputImports = JavaImporter(
            com.terminalfour.media.utils.ImageInfo,
            com.terminalfour.spring.ApplicationContextProvider,
            com.terminalfour.publish.utils.BrokerUtils,
            com.terminalfour.media.IMediaManager,
            com.terminalfour.media.utils.MediaUtils
        );
        with(FullListOutputImports) {
     
            function processTags(t4Tag, contentID, sectionID) {
                var oContent = content || null;
                var CachedSection = section;
                if (typeof contentID !== 'undefined') {
                    if (typeof sectionID === 'undefined') {
                        document.write('Error: sectionID is undefined');
                        return '';
                    }
                    oContent = utils.getContentFromId(contentID);
                    CachedSection = utils.getCachedSectionFromId(sectionID);
                    if (CachedSection == '' || !oContent) {
                        document.write('Error getting the custom content and section');
                        return '';
                    }
                }
                return com.terminalfour.publish.utils.BrokerUtils.processT4Tags(dbStatement, publishCache, CachedSection, oContent, language, isPreview, t4Tag);
            }
     
            function getImageWidth(mediaObj){
                return MediaUtils.getDimension(publishCache, mediaObj, MediaUtils.WIDTH);
            }
     
            function getImageHeight(mediaObj){
                return MediaUtils.getDimension(publishCache, mediaObj, MediaUtils.HEIGHT);
            }
     
            function pxlCrop() {
     
                var mediaManager, thisMediaObject, originalImageWidth, originalImageHeight,
                mediaID = content.getID(),
                language = 'en';
                 
                /* Get the image and find out its width and height */
                mediaManager = ApplicationContextProvider.getBean (IMediaManager);
                thisMediaObject = mediaManager.get(mediaID, language).getContent();
                originalImageWidth = getImageWidth(thisMediaObject);
                originalImageHeight= getImageHeight(thisMediaObject);
     
                /* get the path for the image - this will be the returned with the CDN URL and filter applied */
                var imagePath = processTags('<t4 type="content" name="Media" output="file" />');
                var imagePathParts = imagePath.split('/');
     
                /* Check the path returned has at least the number of parts required */
                if (imagePathParts.length < 5) {
                    throw 'Image Path doesn\'t contain all parts, please check CDN filter is set up and applied correctly';
                }
     
                /*
                    Check the 4th item of the array - this will contain the dimensions of the new image we need to crop to and the crop from point
                    This should get the format widthxheightxcropFromPoint
                    e.g. 750x950xtop
                */
                var imageDimensions = imagePathParts[4];
                var imageDimensionsParts = imageDimensions.split('x');
     
                if (imageDimensionsParts.length < 2) {
                    throw 'Image Dimensions not found, please check CDN filter is set up correctly';
                }
                /* We're good - get the width and height of the new image and crop from point */
                var width = imageDimensionsParts[0];
                var height = imageDimensionsParts[1];
                var cropFrom = 'middle';
                if (typeof imageDimensionsParts[2] !== 'undefined') {
                    cropFrom = imageDimensionsParts[2];
                }
                 
                /* Init vars for the new image we want */
                var aspectRatio, newImageWidth, newImageHeight, crop, cropStartX, cropStartY, cropEndX, cropEndY, newImagePath;
     
                /* Calculate the aspect ratio of the new image */
                aspectRatio = height/width;
                /* And aspect ratio of the image we are cropping */
                originalImageAspectRatio = originalImageHeight/originalImageWidth;
             
                /* Work out if we need crop horizontally or vertically */
                if (originalImageAspectRatio > aspectRatio) {
                    //multiply width of uploaded image by height/width of desired image to get the necessary height.
                    newImageHeight = Math.round(originalImageWidth * (height/width));
                    newImageWidth = originalImageWidth;
                     
                    if (cropFrom == 'top') {
                        //cropping height from top...
                        cropStartX = 0;
                        cropStartY = 0;
                        cropEndX = originalImageWidth;
                        cropEndY = newImageHeight;
                    } else if (cropFrom == 'middle') {
                        //cropping height from middle...
                        cropStartX = 0;
                        cropStartY = Math.round((originalImageHeight - newImageHeight) / 2);
                        cropEndX = originalImageWidth;
                        cropEndY = cropStartY + newImageHeight;
                    } else {
                        //cropping height from bottom...
                        cropStartX = 0;
                        cropStartY = Math.round((originalImageHeight - newImageHeight));
                        cropEndX = originalImageWidth;
                        cropEndY = originalImageHeight;
                    }
                } else {
                    //multiply height of uploaded image by width/height of desired image to get the necessary width.
                    newImageWidth = Math.round(originalImageHeight * (width/height));
                    newImageHeight = originalImageHeight;
             
                    if (cropFrom == 'top') {
                        //cropping width from left...
                        cropStartX = 0;
                        cropStartY = 0;
                        cropEndX = newImageWidth;
                        cropEndY = originalImageHeight;
                    } else if (cropFrom == 'middle') {
                        //cropping width from middle...
                        cropStartX = Math.round((originalImageWidth - newImageWidth) / 2);
                        cropStartY = 0;
                        cropEndX = cropStartX + newImageWidth;
                        cropEndY = originalImageHeight;
                    } else {
                        //cropping width from right...
                        cropStartX = Math.round((originalImageWidth - newImageWidth));
                        cropStartY = 0;
                        cropEndX = originalImageWidth;
                        cropEndY = originalImageHeight;
                    }
                }
             
                /* assemble coordinates for cropping */
                crop = cropStartX + 'x' + cropStartY + ':' + cropEndX + 'x' + cropEndY;
     
                /* Build our new image path */
                newImagePath = '';
                for (i=0; i<imagePathParts.length; i++) {
                    //put in the width and height, this remove the crop from value
                    if (i == 4) {
                        newImagePath += width + 'x' + height;
                    } else {
                        newImagePath += imagePathParts[i];
                    }
                    newImagePath += '/';
                }
             
                /*
                    Return new image path
                    - replace CROP placeholder with crop coordinates
                    - trim / from the end of the path
                */
                return newImagePath.replace('CROP', crop).replace(/^\/+|\/+$/g, '');
            }
     
            var output = pxlCrop();
            document.write(output);
            document.write('');
        }
    } catch (err) {
        var contentID = typeof content !== 'undefined' ? ' content ID: ' + content.getID() : '';
        var sectionID = typeof section !== 'undefined' ? ' section ID: ' + section.getID() : '';
        var message = 'Programmable Layout Error: ' + err + ' occurred in ' + contentID + sectionID + ')';
        var outputImports = JavaImporter(
            org.apache.commons.lang.StringEscapeUtils,
            java.lang.System
        );
        with(outputImports) {
            if (isPreview) {
                document.write(message);
            } else {
                document.write('');
            }
            System.out.println(message);
        }
    }
  3. Add the new Content Layout to the relevant Media Type:
    1. Go to Administration > Settings > Media Library
    2. Click the Media Types tab
    3. Select the relevant Media type e.g., Image
    4. Click the Add row button, select image/pxl-crop from the drop down list and Save changes

How To Crop An Image With PXL

In this example we'll create a PXL filter to crop a 750px x 950px image.

  1. Create a PXL filter. Start off adding your filter rules as you usually for a would for a filter to resize an image following the PXL documentation e.g.:

    fit-in/750x950/filters:quality(95)

    After the height add a value to tell PXL where to start the crop of the image from: top/middle/bottom, here we are using middle and add the CROP placeholder at the end of the filter e.g.:

    fit-in/750x950xmiddle/filters:quality(95)/CROP
  2. Go to your Content Type, open the relevant content layout and generate a T4 Tag:
    Element Value
    Content element Select element for the Image
    Media formatter image/pxl-crop
    CDN / PXL options Check Serve this file from the CDN and Apply a PXL filter to this image
    PXL filter Select the filter created in step 1

    Your generated tag will look something like:
    <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="61" />
  3. Add the T4 tag to your content layout and save. To verify the cropping is working when previewing content, inspect the code using browser dev tools and it should look something like this:
    Screenshot showing example of how a URL of a image will look like in preview with cropping applied
    Note how the value added to the dimensions xmiddle is gone, this is correct. And the CROP placeholder has been replaced with coordinates PXL needs to crop the new image.

How To Let The User Choose The Crop Point 

In this example we'll create a PXL filter to crop a 750px x 950px image and allow the Terminalfour user to choose whether the new image is cropped from the top, middle or bottom.

  1. Create a 3 PXL filters. Start off adding your filter rules as you usually for a would for a filter to resize an image following the PXL documentation e.g.:

    fit-in/750x950/filters:quality(95)

    Add a filter for top, middle and bottom:

    fit-in/750x950xtop/filters:quality(95)/CROP 
    fit-in/750x950xmiddle/filters:quality(95)/CROP
    fit-in/750x950xbottom/filters:quality(95)/CROP
  2. Go to your content type and generate 3 T4 Tags, 1 for each of the filters. Make a note of them as you will need them for the step 3.
    Element Value
    Content element Select element for the Image
    Media formatter image/pxl-crop
    CDN / PXL options Check Serve this file from the CDN and Apply a PXL filter to this image
    PXL filter Select each of the filters created in step 1

    Your generated tags will look something like:

    <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="60" />
    <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="61" />
    <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="62" />
  3. Create a list:
    Element Value
    Name [content type name] crop from positions
    Description List to hold PXL filters used from cropping the image
    And add the T4 tags generated in step 2 as list items:
    Name Value Selected
    Top / Left <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="60" />  
    Middle <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="61" /> x
    Bottom / Right <t4 type="content" name="Image" output="normal" formatter="image/pxl-crop" cdn="true" pxl-filter-id="62" />  
  4. Go back to your Content Type, add a new select element using the list created in step 3:
    Name Description Type Required
    Select crop Select where to crop the image from Select, choose list created in step 3 Yes
  5. In your content layout add a T4 Tag to output the value of the Select crop from element e.g.,
    <t4 type="content" name="Mobile crop from" output="normal" display_field="value" />
  6. Add content and preview. The crop of image will reflect the selected option. For example in the screenshot below see how new image is generated cropping from the left, middle and right of the original image:
    Screenshot showing example of how the same image is cropped from the left, middle and right

Please note: if you have multiple Content Types that require the same image crop you can reuse the same list if the element names are the same. If element names are different you will need a different list but you can still use the same PXL filters.