Статьи

Обработка изображений с помощью Rails

Коллекция изображений

Изображения являются важной частью любого приложения. От социальной сети до простого баг-трекера изображения играют важную роль. Тем не менее, управление изображениями не является тривиальной задачей и требует много планирования заранее.

В этой статье позвольте мне продемонстрировать, как этого добиться в Rails. Я собираюсь показать вам, как обрабатывать ваши изображения и создавать несколько версий в бэкэнде. Мы также увидим, как повысить производительность страницы, сжимая эти изображения без потери качества.

Начиная

Примеры в этом руководстве работают на Rails 4.2 с базой данных MongoDb и HAML для визуализации представлений. Однако используемые здесь фрагменты должны быть совместимы с любой версией Rails, хотя и с небольшими отличиями в конфигурации.

Настройка сцены

ImageMagick — это библиотека для обработки изображений в системах POSIX. Если в вашей системе не установлен ImageMagick, его можно установить с помощью диспетчера пакетов для вашей ОС. На Ubuntu:

sudo apt-get -y install imagemagick
sudo apt-get -y install libmagic-dev
sudo apt-get -y install libmagickwand-dev

В Mac OS X я рекомендую использовать Homebrew:

 brew install imagemagick

Теперь нам нужен адаптер Ruby для подключения к собственной библиотеке ImageMagick. Я лично предпочитаю MiniMagick, так как он легкий и выполняет практически все, что требуется типичному приложению:

 # Gemfile

gem 'mini_magick'

Детская площадка

Давайте поиграем с некоторыми функциями MiniMagick, прежде чем строить что-то серьезное. Откройте консоль Rails ( rails c

 # Open an image from a website

image = MiniMagick::Image.open("https://s3.amazonaws.com/StartupStockPhotos/20140808_StartupStockPhotos/85.jpg")

# Get the Image's width
image.width # 4928

# Get the image's height
image.height #3264

Боже мой, это огромно. Давайте посмотрим, сможем ли мы изменить его размер под наш iPad:

 image.resize "2048x1536"

# Now get the image's new width and height

p "Width is => #{image.width} and height is => #{image.height}"

Ну, вот и все. Подождите секунду, где хранится этот измененный файл?

 image.path # temp path

Манипулирующее изображение сохраняется в временной папке и будет смыто. Чтобы сохранить его на диске, просто вызовите метод write:

 image.write "public/uploads/test.jpg"

Преобразование изображения

Одна из самых частых операций, которую вы выполняете, — это преобразование изображений в разные форматы MiniMagick делает это очень просто:

 image.format "png"
image.write "public/uploads/test.png"

Схожу с ума

Вы также можете объединить несколько операций в одном блоке:

 image.combine_options do |i|
  i.resize "2048x1536"
  i.flip
  i.rotate "-45"
  i.blur "0x15"
end
image.write "public/uploads/blur.png"

![Some weird result](blur.png)

Хорошо, я пошел немного за борт здесь. В свою защиту я просто пытаюсь показать все классные вещи, которые вы можете сделать с MiniMagick :).

Теперь давайте посмотрим, как мы можем связать это с нашим приложением Rails.

Загрузка файлов

Carrierwave — замечательная жемчужина, которая упрощает загрузку файлов в Ruby. Он также хорошо взаимодействует с MiniMagick, делая нашу жизнь намного проще.

 # Gemfile

gem 'carrierwave'
gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'

ПРИМЕЧАНИЕ. Если вы используете ActiveRecord или DataMapper, конфигурации будут немного отличаться, и официальная документация Carrierwave покажет вам путь.

Пакет, чтобы получить все эти драгоценные камни:

 bundle install

Создайте наш первый загрузчик:

 #app/uploaders/image_uploader.rb

class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this Uploader:
  storage :file
  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/images"
  end
end

Код здесь не требует пояснений. storage :filestore_dir

Поскольку файлы отправляются через Интернет, рекомендуется всегда фильтровать входящие файлы:

 # app/uploaders/image_uploader.rb
...
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
  %w(jpg jpeg png gif)
end
...

Этот фрагмент отфильтровывает типы файлов, отличные от указанных здесь. Это ни в коем случае не является защитой от дурака, но оно служит фильтром первого уровня против любой атаки препятствий.

Установите этот загрузчик на нашу модель изображения:

 # app/models/image.rb

class Image
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::Paranoia
  include Mongoid::Attributes::Dynamic
  include Rails.application.routes.url_helpers

  mount_uploader :media, ImageUploader, mount_on: :media_filename
end

Отредактируйте файл image_uploader.rb для обработки загруженного изображения:

 # app/uploaders/image_uploader.rb

#.....
process :resize_to_fill => [200, 200]
process :convert => 'png'
#.....

Попробуйте создать новый образ из консоли Rails:

 media = File.open("/Users/test/Desktop/image/jpg")
img = Image.new(:media => media)
img.save

Загруженное изображение доступно в store_dir Однако загруженное изображение немедленно обрабатывается и перезаписывается изображением 200 × 200. У нас не будет копии исходного файла для любых будущих изменений. Чтобы избежать этого, создайте несколько версий файла.

 # app/uploaders/image_uploader.rb

#.....
version :thumb do
  process :resize_to_fit => [100, 100]
  process :convert => 'jpg'
end

version :cover   do
  process :resize_to_fit => [240, 180]
  process :convert => 'jpg'
end

#.....

Это создает 2 новые версии вместе с исходным изображением. Проверьте версии, созданные Carrierwave:

 img.media.versions[:thumb] # returns the thumb image instance
img.media.versions[:cover] # returns the cover image instance

Вы заметили, что эти изображения генерируются мгновенно? Это означает, что преобразование изображения происходит в том же потоке, и выполнение блокируется до его завершения. В производственном приложении нежелательно создавать несколько версий изображения в одном потоке. Вместо этого мы должны обращаться с этим условно.

 # app/uploaders/image_uploader/rb

#....
version :cover, :if => :is_live? do
  process :resize_to_fit => [240, 180]
  process :convert => 'jpg'
end

def is_live?(img = nil)
  @is_live
end

def is_live=(value)
  @is_live = value
end
#....

Теперь, когда мы пытаемся создать новое изображение, кавер-версия не будет сгенерирована. Мы можем вызвать это вручную, просто запустив:

 img.media.is_live = true
img.save
img.media.recreate_versions! :cover

Этот код также выполняется на переднем плане и является блокирующей операцией, но, по крайней мере, он откладывается до последнего возможного момента. Мы можем сделать это еще дальше, запустив это в фоновом режиме с помощью Resque:

 # lib/resque/image_queue.rb
class ImageQueue
  @queue = :image_queue
  def self.perform(image_id)
    image = Image.find image_id
    img.media.is_live= true
    img.save
    img.media.recreate_versions! :cover
  end
end

и поставьте в очередь:

 Resque.enqueue(ImageQueue, img.id.to_s)

Улучшение производительности

Изображения тяжелые и имеют тенденцию замедлять работу сайта. Один из способов уменьшить вес страницы — сжать эти изображения. Carrierwave Image Optimizer помогает нам сжимать наши изображения на лету без каких-либо хлопот.

Добавьте это в свой Gemfile:

 gem 'carrierwave-imageoptimizer'

И отредактируйте image_uploader

 # app/uploaders/image_uploader.rd

#.....

include CarrierWave::ImageOptimizer
process :optimize
process :quality => 100
#....

Это сжимает все изображения без каких-либо визуальных потерь. То, как это работает, заключается в удалении всей метаинформации об изображении. В среднем это уменьшает размер примерно на 20-30%.

Завершение

Обработка изображений — это огромная вертикаль, и мы едва поцарапали поверхность. С его помощью мы можем создать так много классных вещей. Надеюсь, я заинтересовался этой статьей. Пожалуйста, поделитесь своими мыслями в комментариях.