demarches-normaliennes/app/services/watermark_service.rb
2024-08-22 09:26:48 +02:00

120 lines
3.6 KiB
Ruby

# frozen_string_literal: true
class WatermarkService
POINTSIZE = 20
KERNING = 1.2
ANGLE = 45
FILL_COLOR = "rgba(0,0,0,0.4)"
attr_reader :text
attr_reader :text_length
def initialize(text = APPLICATION_NAME)
@text = " #{text} " # give more space around each occurence
@text_length = @text.length
end
def process(file, output)
metadata = image_metadata(file)
return if metadata.blank?
watermark_image(file, output, metadata)
output
end
private
def watermark_image(file, output, metadata)
MiniMagick::Tool::Convert.new do |convert|
setup_conversion_commands(convert, file)
apply_watermark(convert, metadata)
convert << output.to_path
end
end
def setup_conversion_commands(convert, file)
convert << file.to_path
convert << "-pointsize"
convert << POINTSIZE
convert << "-kerning"
convert << KERNING
convert << "-fill"
convert << FILL_COLOR
convert << "-gravity"
convert << "northwest"
end
# Parcourt l'image ligne par ligne et colonne par colonne en y apposant un filigrane
# en alternant un décalage horizontal sur chaque ligne
def apply_watermark(convert, metadata)
stride_x, stride_y, initial_offsets_x, initial_offset_y = calculate_watermark_params
0.step(by: stride_y, to: metadata[:height] + stride_y * 2).with_index do |offset_y, index|
initial_offset_x = initial_offsets_x[index % 2]
0.step(by: stride_x, to: metadata[:width] + stride_x * 2) do |offset_x|
x = initial_offset_x + offset_x
y = initial_offset_y + offset_y
draw_text(convert, x, y)
end
end
end
def calculate_watermark_params
# Approximation de la longueur du texte, qui marche bien pour les constantes par défaut
char_width_approx = POINTSIZE / 2
char_height_approx = POINTSIZE * 3 / 4
# Dimensions du rectangle de texte
text_width_approx = char_width_approx * text_length * Math.cos(ANGLE * (Math::PI / 180)).abs
text_height_approx = char_width_approx * text_length * Math.sin(ANGLE * (Math::PI / 180)).abs + char_height_approx
diagonal_length = Math.sqrt(text_width_approx**2 + text_height_approx**2)
# Calcul des décalages entre chaque colonne et ligne
# afin que chaque occurence "suive" la précédente
stride_x = ((diagonal_length + char_width_approx) / Math.cos(ANGLE * (Math::PI / 180)))
stride_y = text_height_approx
initial_offsets_x = [0, (0 - stride_x / 2).round] # Motif de damier en alternant le décalage horizontal
initial_offset_y = 0 - stride_y # Offset négatif pour mieux couvrir le nord ouest
[stride_x.round, stride_y.round, initial_offsets_x, initial_offset_y.round]
end
def draw_text(convert, x, y)
# A chaque insertion de texte, positionne le curseur, définit la rotation, puis réinitialise ces paramètres pour la prochaine occurence
# Note: x and y can be negative value
convert << "-draw"
convert << "translate #{x},#{y} rotate #{-ANGLE} text 0,0 '#{text}' rotate #{ANGLE} translate #{-x},#{-y}"
end
def image_metadata(file)
read_image(file) do |image|
width = image.width
height = image.height
if rotated_image?(image)
width, height = height, width
end
{ width: width, height: height }
end
end
def read_image(file)
image = MiniMagick::Image.new(file.to_path)
if image.valid?
yield image
else
Rails.logger.info "Skipping image analysis because ImageMagick doesn't support the file #{file}"
nil
end
end
def rotated_image?(image)
['RightTop', 'LeftBottom'].include?(image["%[orientation]"])
end
end