Laravel Content Aware Media Crop Platform

Eugene Musebe

Introduction

In this article, we will implement Cloudinary’s Content-Aware Crop API to automatically crop and scale our media files to perfectly fit any layout and device.

PHPSandbox and Github

The final project can be viewed on PHPSandbox and the entire source code is available on my Github repository.

Prerequisites

Using Cloudinary in your Laravel projects is pretty straightforward. However, for you to be able to easily follow along, you need to have a good command of your terminal, Git, and entry knowledge of PHP specifically with the Laravel framework.

Getting Started

This assumes you already have composer installed. Laravel uses Composer to manage its dependencies. So, before we can start to ensure you have Composer installed on your machine.

  1. Install Laravel
    • Via Composer: composer create-project --prefer-dist laravel/laravel cloudinary-content-aware
    • Via Laravel Installer composer global requires laravel/installer laravel new cloudinary-content-aware`
    • In step 2 above we have created a project folder called cloudinary-content-aware. Change the directory to this project folder and run the local development server by typing the following commands: cd content-aware php artisan serve`

The Laravel project is now up and running.

Setting up Cloudinary’s Laravel SDK

Using Cloudinary allows you to optimize the performance of your Laravel backend by allowing you to upload, process, and deliver your media files. Cloudinary servers automatically perform optimizations and scales to handle high loads and bursts of traffic with a global content delivery network (CDN) feature. This is great for our media platform.

To implement a content-aware crop for our media files with Cloudinary:

  1. Sign up for a free Cloudinary account then navigate to the Console page and take note of your Cloud name, API Key, and API Secret.

  1. Install Cloudinary’s Laravel SDK: composer require cloudinary-labs/cloudinary-laravel

Note: Please ensure you follow all the steps in the #Installation section. Publish the configuration file and add the Cloudinary credentials you noted in Step 1 to the .env file.

Uploading Image Files

We will be using the Laravel package Livewire to build the UI and the image file upload functionality.

  1. Install Livewire Package by running the following command in your Laravel project:

    composer require livewire/livewire

  1. We will then create a Livewire Component to handle our file uploads:

    php artisan make:livewire FileUpload

This will create two files, one in app/Http/Livewire/FileUpload and the other one in resources/views/livewire/file-upload.blade.php

Now you can use this component anywhere in your Laravel project using the following snippet:

<livewire:file-upload/>

or

@livewire(‘file-upload)

  1. Open resources/views/welcome.blade.php and add the following code:
1<!DOCTYPE html>
2<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1 shrink-to-fit=no">
6
7 <title>Cloudinary Content Aware Crop</title>
8
9 <!-- Fonts -->
10 <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
11
12 <!-- Styles -->
13 @section('styles')
14 <link href="{{ mix('css/app.css') }}" rel="stylesheet">
15 @livewireStyles
16 @show
17</head>
18<body class="antialiased">
19<div
20 class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
21
22 <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
23 <div class="flex align-items-center pt-8 sm:items-center sm:pt-0">
24 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 96.77" width="156" height="30" class="mx-auto">
25 <path
26 d="M160.53 30.41a17.14 17.14 0 0 1 13.56 6.7.69.69 0 0 0 1 .11l5.71-4.55a.71.71 0 0 0 .11-1 26 26 0 0 0-20.61-10.13c-14.91 0-27 12.85-27 28.65s12.13 28.65 27 28.65a25.85 25.85 0 0 0 20.6-10.12.69.69 0 0 0-.12-1l-5.7-4.5a.71.71 0 0 0-1 .11A17.26 17.26 0 0 1 160.53 70c-10.19 0-18.16-8.7-18.16-19.79s7.97-19.8 18.16-19.8ZM188.27 19.91h7.16a.71.71 0 0 1 .71.71V77.4a.7.7 0 0 1-.7.7h-7.16a.71.71 0 0 1-.71-.71V20.62a.7.7 0 0 1 .7-.71ZM220.54 39.55c-9.49 0-19.09 6.72-19.09 19.57 0 11.29 8.21 19.81 19.09 19.81s19.17-8.52 19.17-19.81-8.24-19.57-19.17-19.57Zm10.53 19.57c0 6.52-4.53 11.44-10.53 11.44s-10.44-4.92-10.44-11.44 4.49-11.2 10.44-11.2 10.53 4.81 10.53 11.2ZM278.3 40.37h-7.16a.7.7 0 0 0-.71.7v19c0 7.42-5.12 10.05-9.51 10.05-3.88 0-7.79-2.93-7.79-9.48V41.07a.7.7 0 0 0-.71-.7h-7.16a.7.7 0 0 0-.7.7v20.5c0 11.25 5.09 17.44 14.34 17.44 3.36 0 8.8-1.93 10.84-6.19l.69.14v4.44a.71.71 0 0 0 .71.71h7.16a.71.71 0 0 0 .71-.71V41.07a.7.7 0 0 0-.71-.7ZM322.27 19.91h-7.17a.7.7 0 0 0-.7.71V46l-.44-.7c-2.18-3.51-6.87-5.78-11.95-5.78-8.76 0-17.62 6.75-17.62 19.65 0 11.25 7.61 19.73 17.69 19.73 3.84 0 9.25-1.54 11.88-5.86l.44-.72v5.08a.7.7 0 0 0 .7.71h7.17a.7.7 0 0 0 .7-.71V20.62a.7.7 0 0 0-.7-.71Zm-8 39.21a11 11 0 0 1-10.75 11.36c-5.86 0-10.45-5-10.45-11.36s4.59-11.2 10.45-11.2a11 11 0 0 1 10.72 11.2ZM333 40.37h7.16a.7.7 0 0 1 .7.7V77.4a.7.7 0 0 1-.7.7H333a.71.71 0 0 1-.71-.71V41.07a.71.71 0 0 1 .71-.7ZM336.61 21.06a5.57 5.57 0 0 0-5.69 5.57 5.64 5.64 0 0 0 5.69 5.58 5.54 5.54 0 0 0 5.61-5.58 5.48 5.48 0 0 0-5.61-5.57ZM370.35 39.55c-3.14 0-8.72 1.69-10.85 6.19l-.69-.14v-4.53a.7.7 0 0 0-.71-.7h-7.16a.7.7 0 0 0-.7.7V77.4a.7.7 0 0 0 .7.71h7.16a.71.71 0 0 0 .71-.71v-19c0-7.36 5.12-10 9.51-10 3.88 0 7.79 2.91 7.79 9.4v19.6a.71.71 0 0 0 .71.71H384a.71.71 0 0 0 .71-.71V56.91c-.02-11.19-5.12-17.36-14.36-17.36ZM427.48 40.37h-7.16a.7.7 0 0 0-.71.7v5l-.43-.7c-2.19-3.51-6.88-5.78-12-5.78-8.75 0-17.62 6.75-17.62 19.65 0 11.25 7.61 19.73 17.7 19.73 3.83 0 9.24-1.54 11.88-5.86l.43-.72v5.01a.71.71 0 0 0 .71.71h7.16a.7.7 0 0 0 .7-.71V41.07a.7.7 0 0 0-.66-.7Zm-8 18.75a11 11 0 0 1-10.78 11.36c-5.86 0-10.44-5-10.44-11.36s4.58-11.2 10.44-11.2a11 11 0 0 1 10.76 11.2ZM460.15 40.5a13.66 13.66 0 0 0-5.14-1c-4.76 0-8.22 2.85-10 8.25l-.64-.09v-6.59a.7.7 0 0 0-.71-.7h-7.16a.7.7 0 0 0-.71.7V77.4a.71.71 0 0 0 .71.71h7.24a.7.7 0 0 0 .7-.71V65c0-14.8 5.91-17 9.44-17a11 11 0 0 1 4.33.9.72.72 0 0 0 .61 0 .7.7 0 0 0 .36-.48l1.42-7.11a.71.71 0 0 0-.45-.81ZM499.88 40.68a.69.69 0 0 0-.59-.31h-7.71a.72.72 0 0 0-.66.45L481.59 65l-9.42-24.18a.72.72 0 0 0-.66-.45h-7.86a.69.69 0 0 0-.58.31.7.7 0 0 0-.07.66l14 34.38-7.73 20.09a.71.71 0 0 0 .66 1h7.5a.69.69 0 0 0 .65-.45l21.86-55a.69.69 0 0 0-.06-.68ZM97.91 28.11A40.38 40.38 0 0 0 59.73 0 39.62 39.62 0 0 0 24.6 20.87a29.88 29.88 0 0 0-7.21 56.56l.75.34h.05v-8.5a22.29 22.29 0 0 1 9.29-41.16l2.1-.22.92-1.89A32.15 32.15 0 0 1 59.73 7.57a32.7 32.7 0 0 1 31.55 25l.72 2.86h3a18.53 18.53 0 0 1 18.15 18.46c0 7.05-4.07 12.82-11 15.74v8.06l.5-.16c11.14-3.65 18.06-12.71 18.06-23.64a26.19 26.19 0 0 0-22.8-25.78Z"></path>
27 <path
28 d="m45.07 76.79 1.66 1.66a.33.33 0 0 1-.23.56H33.4a6 6 0 0 1-6-6V47.57a.33.33 0 0 0-.33-.33h-2.8a.33.33 0 0 1-.24-.56l11.12-11.12a.33.33 0 0 1 .47 0l11.11 11.12a.33.33 0 0 1-.23.56h-2.84a.34.34 0 0 0-.34.33v25a6 6 0 0 0 1.75 4.22ZM69.64 76.79l1.67 1.66a.33.33 0 0 1-.24.56H58a6 6 0 0 1-6-6V54a.34.34 0 0 0-.33-.34h-2.83a.33.33 0 0 1-.23-.56L59.72 42a.33.33 0 0 1 .47 0l11.12 11.08a.33.33 0 0 1-.24.56h-2.84a.34.34 0 0 0-.33.34v18.59a6 6 0 0 0 1.74 4.22ZM94.22 76.79l1.66 1.66a.33.33 0 0 1-.23.56H82.54a6 6 0 0 1-6-6V60.38a.33.33 0 0 0-.33-.33h-2.8a.33.33 0 0 1-.23-.57L84.3 48.37a.32.32 0 0 1 .46 0l11.12 11.11a.33.33 0 0 1-.23.57H92.8a.33.33 0 0 0-.33.33v12.19a6 6 0 0 0 1.75 4.22Z"></path>
29 </svg>
30 </div>
31 <h2 class="m-3 mx-auto text-center fw-bold">Content Aware Media Crop Platform</h2>
32 <p class="m-4">
33 Cloudinary uses AI to crop your media files to perfectly fit any design or layout on any device. This is suitable
34 for handling aspect ratios for different social media network requirements.
35 </p>
36
37 <div class="m-2 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
38 @livewire('file-upload')
39 </div>
40
41 <div class="flex justify-center mt-4 mb-4 sm:items-center sm:justify-between">
42 <div class="ml-4 text-center text-sm text-gray-500 sm:text-center sm:ml-0 mx-auto">
43 Powered by <a href="https://cloudinary.com">Cloudinary</a>
44 </div>
45 </div>
46 </div>
47</div>
48@section('scripts')
49<script src="{{ mix('js/app.js') }}"></script>
50<script>
51 if (window.self !== window.top) {
52 window.top.location.href = '{{ url(' / ') }}';
53 }
54</script>
55@livewireScripts
56@show
57</body>
58</html>

This basically adds Livewire styles and scripts, a bunch of HTML code for the project, and the Livewire component we created earlier. Please ensure you replace all the code in the file resources/view/welcome.blade.php.

  1. Open the file resources/views/livewire/file-upload.blade.php and populate it with the following code:
1<div>
2 <div class="flex-row">
3 <div class="spinner-border spinner-border-sm m-3 end-0" role="status" wire:loading wire:target="upload"></div>
4 </div>
5 @if (session()->has('message'))
6 <div class="alert alert-success alert-block m-3">
7 <button type="button" class="close" data-dismiss="alert">×</button>
8 <strong>{{ session('message') }}</strong>
9 </div>
10 @endif
11 <div class="flex h-screen justify-center items-center">
12 <div class="row w-75">
13 <div class="col-md-12">
14 <form class="mb-5" wire:submit.prevent="upload">
15 <div class="form-group row mt-5 mb-3">
16 <div class="input-group">
17 <input type="file" class="form-control @error('media') is-invalid @enderror"
18 placeholder="Choose file..." id="media-file" type="file" wire:model="media">
19 @error('media')
20 <div class="invalid-feedback">{{ $message }}</div>
21 @enderror
22 </div>
23 <small class="text-muted text-center mt-2" wire:loading wire:target="media">
24 {{ __('Uploading') }}&hellip;
25 </small>
26 </div>
27 <div class="text-center">
28 <button type="submit" class="btn btn-sm btn-primary w-25">
29 <i class="fas fa-check mr-1"></i> {{ __('Crop') }}
30 </button>
31 </div>
32 </form>
33 </div>
34 </div>
35 </div>
36 <div class="container">
37 <div class="row">
38 @foreach($croppedImages as $key => $link)
39 <div class="col-sm mb-4">
40 <div class="card">
41 <div class="card-body">
42 <img class="card-img-top" src="{{ $link }}" alt="Card image cap">
43 <h5 class="card-title mt-4 fw-bold">
44 {{ $key }}
45 </h5>
46 @if($key == '1:1')
47 <p>Image Aspect Ratio 1:1</p>
48 <p><strong>Platforms: </strong>Facebook, Instagram</p>
49 @elseif($key == '2:1' || $key == '16:10')
50 <p>Image Aspect Ratio 2:1, 16:10</p>
51 <p><strong>Platforms: </strong>Twitter, LinkedIn</p>
52 @endif
53 </div>
54 </div>
55 </div>
56 @endforeach
57 </div>
58 </div>
59</div>

This is our Livewire Component view, this basically will show the upload media file form and on successful processing through Cloudinary will display the media file uploaded perfectly cropped for the different aspect ratios we will specify. Note: you will see the implementation in code shortly.

  1. Open the file app/Http/Livewire/FileUpload.php. Here, we are going to add a method that will upload an image to Cloudinary applying the necessary transformations that we need. Add the following code in this file.
    11. First we use Livewires `WithFileUploads` to help us with file uploads, then create two variables `$media`
    2 and `$croppedImages` which is an array that will contain the image URLs we get back from Cloudinary.
    1use Livewire\WithFileUploads;
    2
    3 public $media;
    4 public $croppedImages = [];
    5 ```
    6 2. Secondly, we will create the function that will upload the image file to [Cloudinary](https://cloudinary.com) and
    7 apply specified transformations which include the aspect rations that we need.
    8```php
    9public function upload() {
    10 $data = $this->validate([
    11 'media' => ['required',
    12 'image',
    13 'mimes:jpeg,jpg,png',],]);
    14 if (empty($data['media'])) {
    15 unset($data['media']);
    16 } else {
    17 $media = $data['media'];
    18 $aspect_ratio = ['1:1', '2:1', '16:10'];
    19 foreach ($aspect_ratio as $ac) {
    20 $image = cloudinary()->upload($media->getRealPath(), [
    21 'folder' => 'cloudinary-content-aware',
    22 'transformation' => [
    23 'quality' => 'auto',
    24 'fetch_format' => 'auto',
    25 'aspect_ratio' => $ac,
    26 'gravity' => 'faces',
    27 'crop' => 'fill'
    28 ]])->getSecurePath();
    29
    30 $this->croppedImages[$ac] = $image;
    31 }
    32 }
    33
    34 session()->flash('message', 'Image file cropped successfully!');
    35}
    The code above, slated for image files with focus items for instance faces, will automagically focus on the people's faces or any focus items and perform content-aware cropping and resizing of the image based on our parameters. Better still, Cloudinary will also automatically optimize the image size with no compromise in quality. This is done by setting the auto value for the quality and fetch_format attributes.

If you successfully implemented the code above, you should be able to see the following when you navigate to your project on the browser:

Note: Cloudinary is super powerful for the management of your media assets in your project that will not only optimize your assets for visual quality but also cost savings in terms of performance, storage, AI-powered transformations as well.

Do More with Cloudinary

Cloudinary is your A to Z media management solution - upload, storage, administration, manipulation, optimization and delivery.

Get started with Cloudinary in your Laravel projects for FREE!

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.