From 201b31bf36a651647e9c317512da9f32e75f20a5 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 7 Sep 2023 18:10:26 +0200 Subject: [PATCH 1/2] refactor(watermark): small text based watermarked to increase document visibility --- app/assets/images/watermark.png | Bin 9935 -> 0 bytes app/jobs/titre_identite_watermark_job.rb | 65 +------------ app/services/watermark_service.rb | 118 +++++++++++++++++++++++ config/env.example.optional | 3 - config/initializers/watermark.rb | 1 - lib/tasks/pjs.rake | 28 ++++++ spec/services/watermark_service_spec.rb | 23 +++++ 7 files changed, 172 insertions(+), 66 deletions(-) delete mode 100644 app/assets/images/watermark.png create mode 100644 app/services/watermark_service.rb delete mode 100644 config/initializers/watermark.rb create mode 100644 spec/services/watermark_service_spec.rb diff --git a/app/assets/images/watermark.png b/app/assets/images/watermark.png deleted file mode 100644 index 07114bdcfe14c6897e0d4c648906a42887528909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9935 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 diff --git a/app/jobs/titre_identite_watermark_job.rb b/app/jobs/titre_identite_watermark_job.rb index 580dc204d..491530d05 100644 --- a/app/jobs/titre_identite_watermark_job.rb +++ b/app/jobs/titre_identite_watermark_job.rb @@ -10,77 +10,18 @@ class TitreIdentiteWatermarkJob < ApplicationJob # (to avoid modifying the file while it is being scanned). retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10 - MAX_IMAGE_SIZE = 1500 - SCALE = 0.9 - WATERMARK = URI.parse(WATERMARK_FILE).is_a?(URI::HTTP) ? WATERMARK_FILE : Rails.root.join("app/assets/images/#{WATERMARK_FILE}") - def perform(blob) return if blob.watermark_done? raise FileNotScannedYetError if blob.virus_scanner.pending? blob.open do |file| - watermark = resize_watermark(file) - - if watermark.present? - processed = watermark_image(file, watermark) + Tempfile.create(["watermarked", File.extname(file)]) do |output| + processed = WatermarkService.new.process(file, output) + return if processed.blank? blob.upload(processed) blob.touch(:watermarked_at) end end end - - private - - def watermark_image(file, watermark) - ImageProcessing::MiniMagick - .source(file) - .convert("png") - .resize_to_limit(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) - .composite(watermark, mode: "over", gravity: "center") - .call - end - - def resize_watermark(file) - metadata = image_metadata(file) - - if metadata[:width].present? && metadata[:height].present? - width = [metadata[:width], MAX_IMAGE_SIZE].min * SCALE - height = [metadata[:height], MAX_IMAGE_SIZE].min * SCALE - diagonal = Math.sqrt(height**2 + width**2) - angle = Math.asin(height / diagonal) * 180 / Math::PI - - ImageProcessing::MiniMagick - .source(WATERMARK) - .resize_to_limit(diagonal, diagonal / 2) - .rotate(-angle, background: :transparent) - .call - end - end - - def image_metadata(file) - read_image(file) do |image| - if rotated_image?(image) - { width: image.height, height: image.width } - else - { width: image.width, height: image.height } - end - end - end - - def read_image(file) - require "mini_magick" - image = MiniMagick::Image.new(file.path) - - if image.valid? - yield image - else - logger.info "Skipping image analysis because ImageMagick doesn't support the file" - {} - end - end - - def rotated_image?(image) - ['RightTop', 'LeftBottom'].include?(image["%[orientation]"]) - end end diff --git a/app/services/watermark_service.rb b/app/services/watermark_service.rb new file mode 100644 index 000000000..1f192f879 --- /dev/null +++ b/app/services/watermark_service.rb @@ -0,0 +1,118 @@ +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 diff --git a/config/env.example.optional b/config/env.example.optional index 8935825e9..969c105d3 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -68,9 +68,6 @@ DS_ENV="staging" # Instance customization: PDF export logo ---> to be put in "app/assets/images" # DOSSIER_PDF_EXPORT_LOGO_SRC="app/assets/images/header/logo-ds-wide.png" -# Instance customization: watermark for identity documents -# WATERMARK_FILE="" - # Enabling maintenance mode # MAINTENANCE_MODE="true" diff --git a/config/initializers/watermark.rb b/config/initializers/watermark.rb deleted file mode 100644 index 8c87c9cda..000000000 --- a/config/initializers/watermark.rb +++ /dev/null @@ -1 +0,0 @@ -WATERMARK_FILE = ENV.fetch('WATERMARK_FILE', 'watermark.png') diff --git a/lib/tasks/pjs.rake b/lib/tasks/pjs.rake index 6b52198fc..73d952e3e 100644 --- a/lib/tasks/pjs.rake +++ b/lib/tasks/pjs.rake @@ -22,4 +22,32 @@ namespace :pjs do blobs.in_batches { |batch| batch.ids.each { |id| PjsMigrationJob.perform_later(id) } } end + + desc "Watermark demo. Usage: noglob rake pjs:watermark_demo[tmp/carte-identite-demo-1.jpg]" + task :watermark_demo, [:file_path] => :environment do |_t, args| + file = Pathname.new(args[:file_path]) + output_file = Rails.root.join('tmp', "#{file.basename(file.extname)}_watermarked#{file.extname}") + + processed = WatermarkService.new.process(file, output_file) + + if processed + rake_puts "Watermarked: #{processed}" + else + rake_puts "File #{file} not watermarked. Read application log for more information" + end + end + + desc "Watermark demo all defined demo files. Usage: noglob rake pjs:watermark_demo_all" + task :watermark_demo_all => :environment do + # You must have these filenames in tmp/ to run this demo (download ID cards specimens) + filenames = [ + "carte-identite-demo-1.jpg", "carte-identite-demo-2.jpg", "carte-identite-demo-3.png", "carte-identite-demo-4.jpg", + "carte-identite-demo-5.jpg", "passeport-1.jpg", "passeport-2.jpg" + ] + + filenames.each do |file| + Rake::Task["pjs:watermark_demo"].invoke("tmp/#{file}") + Rake::Task["pjs:watermark_demo"].reenable + end + end end diff --git a/spec/services/watermark_service_spec.rb b/spec/services/watermark_service_spec.rb new file mode 100644 index 000000000..abe0da8b4 --- /dev/null +++ b/spec/services/watermark_service_spec.rb @@ -0,0 +1,23 @@ +RSpec.describe WatermarkService do + let(:image) { file_fixture("logo_test_procedure.png") } + let(:watermark_service) { WatermarkService.new } + + describe '#process' do + it 'returns a tempfile if watermarking succeeds' do + Tempfile.create do |output| + watermark_service.process(image, output) + # output size should always be a little greater than input size + expect(output.size).to be_between(image.size, image.size * 1.5) + end + end + + it 'returns nil if metadata is blank' do + allow(watermark_service).to receive(:image_metadata).and_return(nil) + + Tempfile.create do |output| + expect(watermark_service.process(image.to_path, output)).to be_nil + expect(output.size).to eq(0) + end + end + end +end From 6520ea027c0dd1467126d4c5abeb4c944aeaf88a Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Mon, 11 Sep 2023 14:55:23 +0200 Subject: [PATCH 2/2] ci: install a font for watermarks --- .github/workflows/ci.yml | 3 +++ README.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 941671096..3bf5b4b1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,9 @@ jobs: - name: Setup the app runtime and dependencies uses: ./.github/actions/ci-setup-rails + - name: Install fonts pickable by ImageMagick + run: sudo apt-get install -y gsfonts + - name: Pre-compile assets uses: ./.github/actions/ci-setup-assets diff --git a/README.md b/README.md index a427f1fe2..add1b33f2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Vous souhaitez y apporter des changements ou des améliorations ? Lisez notre [ #### Tous environnements - postgresql +- imagemagick et gsfonts pour générer les filigranes sur les titres d'identité. #### Développement