Class: JekyllImgFlow::Providers::Sharp

Inherits:
CliBase show all
Defined in:
lib/jekyll-imgflow/providers/sharp.rb

Overview

Sharp provider implementation using the standardized tag interface

Constant Summary

Constants inherited from BaseProvider

BaseProvider::COMPASS_TO_SHORT, BaseProvider::KNOWN_OPERATIONS, BaseProvider::SMARTCROP_POSITIONS

Instance Attribute Summary

Attributes inherited from BaseProvider

#config

Instance Method Summary collapse

Methods inherited from CliBase

#execute

Methods inherited from BaseProvider

#add_watermark, #alpha_byte_value, #alpha_opacity=, #check_http_service, #cli_available?, #compass_to_short, #convert_format, #crop, #crop_coords, #crop_geometry, #encode_file_url, #execute, #find_op, #format, #initialize, #input_format_ext, #op?, #opacity, #operations, #optimize, provider_name, #quality, #quality=, #relative_file_path, #reset_operations, #resize, #smartcrop_interestingness, #supports_operation?, supports_operation?, #temp_path, unsupported_operations, #watermark, #watermark_parts

Constructor Details

This class inherits a constructor from JekyllImgFlow::Providers::BaseProvider

Instance Method Details

#available? ⇒ Boolean

Returns:

  • (Boolean)


11
12
13
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 11

def available?
  cli_available?("sharp")
end

#build_commands(input_path, output_path) ⇒ Object



15
16
17
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 15

def build_commands(input_path, output_path)
  build_sharp_command(input_path, output_path)
end

#build_composite_command(base_path, output_path) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 55

def build_composite_command(base_path, output_path)
  wm_op = find_op(:watermark)
  parts = watermark_parts(wm_op)
  gravity = translate_position(parts[:position])

  command_parts = ["sharp", "-i", base_path.shellescape,
                   "-o", output_path.shellescape]

  add_format_quality(command_parts)

  if parts[:opacity] && parts[:opacity] < 1.0
    wm_temp = temp_path(parts[:watermark_path], "tmp_wm.png")
    alpha_value = alpha_byte_value(parts[:opacity])
    temp_cmd = ["sharp", "-i", parts[:watermark_path].shellescape,
                "-o", wm_temp.shellescape,
                "ensureAlpha", alpha_value.to_s].join(" ")
    command_parts += ["composite", wm_temp.shellescape,
                      "--gravity", gravity, "--blend", "over"]
    "#{temp_cmd} && #{command_parts.join(' ')} " \
      "&& rm -f #{wm_temp.shellescape}"
  else
    command_parts += ["composite", parts[:watermark_path].shellescape,
                      "--gravity", gravity, "--blend", "over"]
    command_parts.join(" ")
  end
end

#build_copy_command(input_path, output_path) ⇒ Object



145
146
147
148
149
150
151
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 145

def build_copy_command(input_path, output_path)
  # sharp -i input.jpg -o output.jpg -f format -q quality
  command_parts = ["sharp", "-i", input_path.shellescape, "-o", output_path.shellescape]

  add_format_quality_alpha(command_parts)
  command_parts.join(" ")
end

#build_crop_command(input_path, output_path) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 124

def build_crop_command(input_path, output_path)
  crop_op = find_op(:crop)
  geo = crop_geometry(crop_op)

  if geo[:smartcrop]
    # Use smartcrop for intelligent cropping (Sharp uses libvips backend)
    interestingness = smartcrop_interestingness(geo[:keep])

    # sharp -i input.jpg -o output.jpg smartcrop width height --interesting=attention
    ["sharp", "-i", input_path.shellescape, "-o", output_path.shellescape,
     "smartcrop", geo[:width].to_s, geo[:height].to_s,
     "--interesting=#{interestingness}"].join(" ")
  else
    # Use basic extract for manual cropping or when no keep parameter
    # sharp -i input.jpg -o output.jpg extract top left width height
    ["sharp", "-i", input_path.shellescape, "-o", output_path.shellescape,
     "extract", geo[:y].to_s, geo[:x].to_s, geo[:width].to_s,
     geo[:height].to_s].join(" ")
  end
end

#build_resize_command(input_path, output_path) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 107

def build_resize_command(input_path, output_path)
  resize_op = find_op(:resize)

  # sharp -i input.jpg -o output.jpg resize width [height] -f format -q quality
  command_parts = ["sharp", "-i", input_path.shellescape, "-o", output_path.shellescape]

  # Add resize
  command_parts += if resize_op[:height]
                     ["resize", resize_op[:width].to_s, resize_op[:height].to_s]
                   else
                     ["resize", resize_op[:width].to_s]
                   end

  add_format_quality_alpha(command_parts)
  command_parts.join(" ")
end

#build_sequential_crop_resize(input_path, output_path) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 93

def build_sequential_crop_resize(input_path, output_path)
  # Build temp file path for intermediate crop result
  temp = temp_path(input_path, "tmp_crop.jpg")

  # First command: crop only
  crop_cmd = build_crop_command(input_path, temp)

  # Second command: resize + format + quality + alpha (all together)
  resize_cmd = build_resize_command(temp, output_path)

  # Combine with && and cleanup temp file
  "#{crop_cmd} && #{resize_cmd} && rm -f #{temp.shellescape}"
end

#build_sharp_command(input_path, output_path) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 19

def build_sharp_command(input_path, output_path)
  if op?(:watermark)
    build_watermark_pipeline(input_path, output_path, op?(:crop), op?(:resize))
  elsif op?(:crop) && op?(:resize)
    build_sequential_crop_resize(input_path, output_path)
  elsif op?(:resize)
    build_resize_command(input_path, output_path)
  elsif op?(:crop)
    build_crop_command(input_path, output_path)
  else
    build_copy_command(input_path, output_path)
  end
end

#build_watermark_pipeline(input_path, output_path, has_crop, has_resize) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 33

def build_watermark_pipeline(input_path, output_path, has_crop, has_resize)
  temp = temp_path(input_path, "tmp_base.jpg")
  commands = []

  if has_crop && has_resize
    commands << build_sequential_crop_resize(input_path, temp)
    base_path = temp
  elsif has_resize
    commands << build_resize_command(input_path, temp)
    base_path = temp
  elsif has_crop
    commands << build_crop_command(input_path, temp)
    base_path = temp
  else
    base_path = input_path
  end

  commands << build_composite_command(base_path, output_path)
  commands << "rm -f #{temp.shellescape}" unless base_path == input_path
  commands.join(" && ")
end

#sharp_format(format) ⇒ Object

Sharp CLI calls JPEG "jpeg", while ImgFlow uses the standard "jpg" extension in generated filenames and public configuration.



155
156
157
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 155

def sharp_format(format)
  format.to_s == "jpg" ? "jpeg" : format
end

#translate_position(position) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/jekyll-imgflow/providers/sharp.rb', line 82

def translate_position(position)
  case position.to_s
  when "northwest" then "northwest"
  when "northeast" then "northeast"
  when "southwest" then "southwest"
  when "southeast" then "southeast"
  when "center" then "center"
  else position.to_s
  end
end