Class: BetterSeo::Image::Optimizer

Inherits:
Object
  • Object
show all
Defined in:
lib/better_seo/image/optimizer.rb

Constant Summary collapse

SUPPORTED_FORMATS =
%w[jpg jpeg png webp gif].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(quality: 85) ⇒ Optimizer

Returns a new instance of Optimizer.



16
17
18
19
# File 'lib/better_seo/image/optimizer.rb', line 16

def initialize(quality: 85)
  @quality = quality
  check_imagemagick!
end

Instance Attribute Details

#qualityObject (readonly)

Returns the value of attribute quality.



14
15
16
# File 'lib/better_seo/image/optimizer.rb', line 14

def quality
  @quality
end

Instance Method Details

#compress(source, destination) ⇒ Object

Compress image



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/better_seo/image/optimizer.rb', line 71

def compress(source, destination)
  check_imagemagick!
  validate_source!(source)

  image = MiniMagick::Image.open(source)
  image.quality @quality
  image.write destination

  destination
rescue MiniMagick::Error => e
  raise ImageError, "Failed to compress: #{e.message}"
end

#convert_to_webp(source, destination) ⇒ Object

Convert image to WebP



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/better_seo/image/optimizer.rb', line 32

def convert_to_webp(source, destination)
  check_imagemagick!
  validate_source!(source)

  image = MiniMagick::Image.open(source)
  image.format "webp"
  image.quality @quality
  image.write destination

  destination
rescue MiniMagick::Error => e
  raise ImageError, "Failed to convert to WebP: #{e.message}"
end

#generate_responsive(source, output_dir, sizes: {}, prefix: "responsive") ⇒ Object

Generate responsive image sizes



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/better_seo/image/optimizer.rb', line 85

def generate_responsive(source, output_dir, sizes: {}, prefix: "responsive")
  check_imagemagick!
  validate_source!(source)

  FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)

  results = {}
  ext = File.extname(source)

  sizes.each do |name, width|
    output_path = File.join(output_dir, "#{prefix}_#{name}_#{width}w#{ext}")
    resize(source, output_path, width: width)
    results[name] = output_path
  end

  results
rescue MiniMagick::Error => e
  raise ImageError, "Failed to generate responsive images: #{e.message}"
end

#optimize(source, destination, resize: nil) ⇒ Object

Optimize image (resize + compress)



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/better_seo/image/optimizer.rb', line 106

def optimize(source, destination, resize: nil)
  check_imagemagick!
  validate_source!(source)

  original_size = File.size(source)

  if resize
    resize(source, destination, **resize)
  else
    compress(source, destination)
  end

  optimized_size = File.size(destination)
  reduction = ((original_size - optimized_size).to_f / original_size * 100).round(2)

  {
    original_size: original_size,
    optimized_size: optimized_size,
    reduction_percent: reduction
  }
rescue MiniMagick::Error => e
  raise ImageError, "Failed to optimize: #{e.message}"
end

#resize(source, destination, width: nil, height: nil) ⇒ Object

Resize image



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/better_seo/image/optimizer.rb', line 47

def resize(source, destination, width: nil, height: nil)
  check_imagemagick!
  validate_source!(source)

  raise ImageError, "Either width or height must be specified" if width.nil? && height.nil?

  image = MiniMagick::Image.open(source)

  if width && height
    image.resize "#{width}x#{height}!"
  elsif width
    image.resize "#{width}x"
  else
    image.resize "x#{height}"
  end

  image.write destination

  destination
rescue MiniMagick::Error => e
  raise ImageError, "Failed to resize: #{e.message}"
end

#validate_format!(path) ⇒ Object

Validate image format



22
23
24
25
26
27
28
29
# File 'lib/better_seo/image/optimizer.rb', line 22

def validate_format!(path)
  ext = File.extname(path).downcase.delete(".")
  unless SUPPORTED_FORMATS.include?(ext)
    raise ImageError, "Unsupported format: #{ext}. Supported: #{SUPPORTED_FORMATS.join(", ")}"
  end

  true
end