2023-09-07 18:10:26 +02:00
|
|
|
class WatermarkService
|
|
|
|
POINTSIZE = 20
|
|
|
|
KERNING = 1.2
|
|
|
|
ANGLE = 45
|
|
|
|
FILL_COLOR = "rgba(0,0,0,0.4)"
|
|
|
|
|
|
|
|
attr_reader :text
|
|
|
|
attr_reader :text_length
|
|
|
|
|
2024-04-23 14:55:37 +02:00
|
|
|
def initialize(text = APPLICATION_NAME)
|
2023-09-07 18:10:26 +02:00
|
|
|
@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
|