How do I use an image CDN to compress photos?

I’m curious if anyone has experience implementing an image CDN like Cloudinary in OJS or other PKP software. Generally, this would involve adding sizing and format information as URL parameters on every image resource. It seems like this could be done with PHP, but I’m not sure of a way it could be done without manually setting each image size. Is there a way of doing this?
image-cdn-url
Maybe there are other solutions too–I’d love to hear them. Perhaps an official CDN plugin would be nice that allowed you to choose a service from a few options and enter your API key.

I’ve looked all over for a post about CDNs but couldn’t find any - would love to see any other discussions you’re familiar with.

Any other thoughts?

After some research in this area that I’m not very familiar with, I’m including some findings below. These are not meant as a how-to by any means, and I would welcome any suggestions and different approaches! You can see a demo here:

Option 1

At the risk of being a bit embarrassed, I’m brute-forcing a solution here client-side. I don’t love it in terms of efficiency, and would welcome any other solutions!

Essentially, I’m just adding the code below using a library from imgix:

Located in <head>

<!-- CDN Configuration -->
<meta property="ix:host" content="abs.imgix.net">
<script src="/public/lib/imgix.js"></script>

Located in <body>

<script type="text/javascript">
/* Scan page for all <img> elements */
var images = Array.prototype.slice.call(document.getElementsByTagName('img'));

/* Set attributes for each image */
images.forEach(update);

function update(item, index) {
  /* Get relative path for image */
  let fullUrl = document.getElementsByTagName('img')[index].getAttribute('src');
  let url = fullUrl.replace("https://journal.bahaistudies.ca/", "");
  /* Calculate rendered image dimenstions */
  let imgWidth = item.clientWidth;
  let imgHeight = item.clientHeight;
  /* Select the current image */
  let doc = document.getElementsByTagName('img')[index];
  /* Set attributes for image */
  doc.removeAttribute('src');
  doc.setAttribute('ix-path', url);
  doc.setAttribute('ix-params', '{ "auto":"format", "min-w":"300" }');
  doc.setAttribute('sizes', imgWidth + "px x " + imgHeight + "px");
}
</script>

Essentially, this is setting imgix parameters which allows the images to be targeted by the imgix library. I don’t love that it’s all client-side, as it adds extra bulk to the renderings process and isn’t preloader friendly. It’s fairly simple first step and overall it does reduce load times for the user however.

Option 2

It occurs to me that an alternative would be to create a plugin that converted images statically on your server like TinyPNG. You would just enter your API key from the service and then the plugin would make all the API calls, perhaps as a CRON job or on every upload. It does seem like maybe some image processing is already taking place locally, but I can’t tell.

Option 3

I suppose a third option would be to set up the server itself to rewrite image URLs to the CDN. Instructions for Apache are here.

Hi @benaltair,

I looked at the demo link but it looks like the images are still attached to your server rather than a CDN. Is that right? How are you getting the images over to the CDN?

We do want to better support remote files and have made some progress towards that with the new 3.3 release. But we’re not quite there yet.

Hi @NateWr, thanks for taking a look. I removed my workaround for now because it didn’t work with externally linked images like the Creative Commons badge. I think ideally, what I’d love is a simple configuration option that altered the resource URLs, and perhaps a second configuration option that allowed you to select formats you’d like to alter. I’m not sure if URL writing is called from some central function or if it’s duplicated where needed.

An alternative would be to allow any local resources to be referred to with relative links rather than the full address.

Either way would allow simple CDNs to be enabled like imgix. Imgix works similarly to other image CDNs I’ve seen. Basically you host the image on your server, and register a subdomain with them that points to your domain. Then any image you want to pull from the CDN, you just set the domain to the imgix subdomain instead, with the exact same path after. If the src path is relative, then you can actually run imgix.js which will add all the relative data attributes to your images client-side.

A method most CDNs employ is transformations. Basically, by passing URL parameters like w=500 it will dynamically resize the image. You can do some really cool things with imgix in particular. For example, in my archives page you can see dynamically generated covers for the issues that don’t have one set. Past Issues | The Journal of Bahá’í Studies

This is done with an {if} statement in PHP that feeds the issue data into a URL which is then requested from imgix. They are unique in that they don’t charge for transformations - just for the total number of active source images you have. I think it’s $3/1000/month.

A second method that most image CDNs seem to rely on is HTML5 <image> tags and the srcset attribute. Either one will allow for a set of different images (which are dynamically generated just by the URL parameters) and then the browser selects which one is best, only loading the right size for the resolution, supported formats, etc.

For example, this is what imgix.js produces when provided with a relative URL:

<img
    class="img-fluid homepage-issue-cover"
    ix-path="public/journals/1/cover_issue_1_en_US.png"
    ix-params='{ "auto":"format", "min-w":"300" }'
    sizes="210px x 307px"
    srcset="
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=100   100w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=116   116w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=134   134w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=156   156w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=182   182w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=210   210w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=244   244w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=282   282w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=328   328w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=380   380w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=442   442w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=512   512w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=594   594w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=688   688w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=798   798w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=926   926w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=1074 1074w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=1246 1246w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=1446 1446w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=1678 1678w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=1946 1946w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=2258 2258w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=2618 2618w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=3038 3038w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=3524 3524w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=4088 4088w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=4742 4742w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=5500 5500w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=6380 6380w,
        https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2&amp;w=7400 7400w
    "
    src="https://abs.imgix.net/public/journals/1/cover_issue_1_en_US.png?auto=format&amp;min-w=300&amp;ixlib=imgixjs-3.4.2"
    ix-initialized="ix-initialized"
/>

I think if one CDN were to be chosen for simplicity to implement with a plugin, imgix would be up there IMO. I did a lot of research in the different options, and it seems like It’s one of the best fits for OJS.