Home > Software design >  laravel picture gallery optimisation
laravel picture gallery optimisation

Time:06-07

I have photo gallery page, which shows all files from given directory. I have following logic of handling pictures:

  • original pictures I keep inside storage/app/img
  • using console command and Intervention\Image library I create thumbs - in public/thumbs_md I keep small thumbs about 300x300 and in public/thumbs_lg I keep the same pictures, but 1024x768.

My idea about gallery page is that I find all files from given directory inside thumbs_md and show them as thumbs with the links to the files with the same names inside thumbs_lg. And each picture should have description, which I take from $image->exif('ImageDescription') using Intervention\Image library. And paginate results with 20 pictures on the page. It works well with less than 200 pictures in the directory, but when it is more nginx gives me 504 Gateway Time-out. I found that the reason in getting description from exif. EXIF info stored only in original source pictures inside storage/app/img. During thumbs generating EXIF is deleted. I could't find a way to keep it. Maybe this is main reason why I get Gateway Time-out. I guess that I have 2 problems:

  1. It takes longer time to get EXIF from big source file rather than from thumb, but I don't know how to copy exif data from source image to thumb.
  2. I retrieve exif data from all files in directory at once, but I don't know how to split it into chunks, because I must to give all elements to paginator at once.

At the moment I just comment code where I read EXIF from all files and there is no problems, but I want to get those descriptions and I don't really want to keep image description in database. I like the fact that it is part of image. Is there a way to optimise my code?

controller

    public function item($slug)
    {

        $files = Storage::disk('storage')->allFiles('thumbs_md/'.$slug);
        $links = Storage::disk('storage')->allFiles('thumbs_lg/'.$slug);

        if ( empty($files) ){
            abort(404);
        }

        // generate title of the page
        try  { 
            $image = Image::make( '/var/www/storage/app/img/categories/'. $slug.'.jpg'); 
        }
        catch(NotReadableException $e)
        {
            // If error
            $title = 'Picture not found';
        }
            // If no error ...
            $title = $image->exif('ImageDescription');


        // generate description for picture
        $ImageDescription = function($value) {
            try  { $image = Image::make(str_replace('thumbs_md', '/var/www/storage/app/img', $value)); }
            catch(NotReadableException $e)
            {
                // If error
                return 'Picture not found';
            }
                // If no error ...
                return $image->exif('ImageDescription');
        };
        //$imgDesc = array_map($ImageDescription, $files); 

        for ($i=0; $i < count($files); $i  ) {
            $items[$i]['thumb'] = $files[$i];
            $items[$i]['link'] = $links[$i];
            //$items[$i]['description'] = $imgDesc[$i];
        }

        $items = Arr::arrayToObject($items); 
        $items = collect($items)->mypaginate($perPage = 20);
        $items->title = $title;
        $items->slug  = $slug;
        
        return view('pages.gallery.item', compact('items'));

    }

view

    <div >

      @foreach ($items as $item)
      <a href="/storage/{{ $item->link }}" > 
        <img {{-- alt="{{ $item->description }}" --}} src="/storage/{{ $item->thumb }}">
        {{-- <p>{{ $item->description }}</p> --}}
      </a>
      @endforeach

    </div>

CodePudding user response:

I don't see why you would not put this stuff in a database, when you've clearly shown that retrieving EXIF data from a big file is slow. You don't want to be doing heavy work on each page load.

You are also limited to filtering data easily as long as it is not in a database table.

The code you have currently is badly written and not performant (you are retrieving the page title from an image???) so the way to go is to design a few database tables to handle this :). Once it is done, I bet you can shrink this controller to under 7 lines.

CodePudding user response:

Intensive or time consuming processing should be avoided during request as far as possible for better user experience.

One way to optimise your workflow would be:

Create database table for categories

Create image_categories table with columns

  • name(string)
  • slug(string)(unique)
  • title(string): exif('ImageDescription') or any value
  • exif(text): whole exif data for the category image if required
Create database table for Images

Create the images table with columns

  • image_category(string): slug for the image category
  • src(string): path to the original image
  • src_thumb_md(string): path to the thumb_md (300 x 300) image
  • src_thumb_lg(string): path to the thumb_lg (1024 x 768) image
  • description(string): exif('ImageDescription')
  • exif(text): whole exif data for the image if required
Store the data when creating thumbnails for image(s)

Extend the console command which you are using to generate the thumbnails, to

  • store the paths and exif data to the images table for all images
  • store the title and exif data to the image_categories table when storing the image for category
Define Image and ImageCategory models with relations (optional)
class ImageCategory extends Model
{
    public function images()
    {
        return $this->hasMany(ImageCategory::class, 'image_category');
    }
}

class Image extends Model
{
    public function imageCategory()
    {
        return $this->belongsTo(ImageCategory::class, 'image_category', 'slug');
    }
}
Controller & View

If Models are defined

public function item($slug)
{
    $category = ImageCategory::where('slug', $slug)->first();

    $images = $category->images()->paginate(20);

    return view('pages.gallery.item', compact('category', 'images'));
}

Or when models are not defined

public function item($slug)
{
    $category = DB::tables('image_categories')->where('slug', $slug)->first();

    $images = DB::table('images')->where('image_category', $slug)->paginate(20);

    return view('pages.gallery.item', compact('category', 'images'));
}
<div >
    <h1>{{ $category->title }}</h1>
    @foreach ($images as $image)
        <a href="/storage/{{ $image->src_thumb_lg }}" > 
            <img {{-- alt="{{ $image->description }}" --}} src="/storage/{{ $image->src_thumb_md }}">
        {{-- <p>{{ $image->description }}</p> --}}
       </a>
    @endforeach

    <!-- Pagination links -->
    {{ $images->links() }}
</div>
  • Related