From 371179dc5b23fce4dcbf3fc910ad819572df71f2 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 17 Nov 2020 16:34:24 +0100 Subject: [PATCH] Watermark titres identite --- Gemfile | 1 + Gemfile.lock | 7 ++ app/assets/images/watermark.png | Bin 0 -> 9935 bytes app/jobs/titre_identite_watermark_job.rb | 70 ++++++++++++++++++ app/models/champs/titre_identite_champ.rb | 10 +-- .../blob_titre_identite_watermark_concern.rb | 23 ++++++ .../editable_champs/_titre_identite.html.haml | 2 +- config/initializers/active_storage.rb | 1 + 8 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 app/assets/images/watermark.png create mode 100644 app/jobs/titre_identite_watermark_job.rb create mode 100644 app/models/concerns/blob_titre_identite_watermark_concern.rb diff --git a/Gemfile b/Gemfile index b3507f972..50ba4515f 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,7 @@ gem 'haml-rails' gem 'hashie' gem 'http_accept_language' gem 'iban-tools' +gem 'image_processing' gem 'jquery-rails' # Use jquery as the JavaScript library gem 'jwt' gem 'kaminari', '1.2.1' # Pagination diff --git a/Gemfile.lock b/Gemfile.lock index ffb7cd371..6e8c7cb6c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -357,6 +357,9 @@ GEM concurrent-ruby (~> 1.0) iban-tools (1.1.0) ice_nine (0.11.2) + image_processing (1.12.1) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) ipaddress (0.8.3) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) @@ -415,6 +418,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2020.0512) mimemagic (0.3.5) + mini_magick (4.11.0) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) @@ -630,6 +634,8 @@ GEM rexml ruby-progressbar (1.10.1) ruby-saml-idp (0.3.5) + ruby-vips (2.0.17) + ffi (~> 1.9) ruby2_keywords (0.0.2) ruby_parser (3.15.0) sexp_processor (~> 4.9) @@ -827,6 +833,7 @@ DEPENDENCIES hashie http_accept_language iban-tools + image_processing jquery-rails jwt kaminari (= 1.2.1) diff --git a/app/assets/images/watermark.png b/app/assets/images/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..07114bdcfe14c6897e0d4c648906a42887528909 GIT binary patch literal 9935 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 literal 0 HcmV?d00001 diff --git a/app/jobs/titre_identite_watermark_job.rb b/app/jobs/titre_identite_watermark_job.rb new file mode 100644 index 000000000..a86465551 --- /dev/null +++ b/app/jobs/titre_identite_watermark_job.rb @@ -0,0 +1,70 @@ +class TitreIdentiteWatermarkJob < ApplicationJob + queue_as :active_storage_watermark + + MAX_IMAGE_SIZE = 1500 + SCALE = 0.9 + WATERMARK = Rails.root.join('app/assets/images/watermark.png') + + def perform(blob) + blob.open do |file| + watermark = resize_watermark(file) + processed = watermark_image(file, watermark) + + blob.metadata[:watermark] = true + blob.upload(processed) + blob.save + 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) + + 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 + + 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/models/champs/titre_identite_champ.rb b/app/models/champs/titre_identite_champ.rb index 460960ed5..4f29e0186 100644 --- a/app/models/champs/titre_identite_champ.rb +++ b/app/models/champs/titre_identite_champ.rb @@ -42,16 +42,10 @@ class Champs::TitreIdentiteChamp < Champ end def for_export - piece_justificative_file.filename.to_s if piece_justificative_file.attached? + nil end def for_api - if piece_justificative_file.attached? && (piece_justificative_file.virus_scanner.safe? || piece_justificative_file.virus_scanner.pending?) - piece_justificative_file.service_url - end - end - - def update_skip_pj_validation - type_de_champ.update(skip_pj_validation: true) + nil end end diff --git a/app/models/concerns/blob_titre_identite_watermark_concern.rb b/app/models/concerns/blob_titre_identite_watermark_concern.rb new file mode 100644 index 000000000..7bbdebd6a --- /dev/null +++ b/app/models/concerns/blob_titre_identite_watermark_concern.rb @@ -0,0 +1,23 @@ +module BlobTitreIdentiteWatermarkConcern + extend ActiveSupport::Concern + + included do + after_update_commit :enqueue_watermark_job + end + + private + + def titre_identite? + attachments.find { |attachment| attachment.record.class.name == 'Champs::TitreIdentiteChamp' } + end + + def watermarked? + metadata[:watermark] + end + + def enqueue_watermark_job + if titre_identite? && !watermarked? && analyzed? && virus_scanner.done? + TitreIdentiteWatermarkJob.perform_later(self) + end + end +end diff --git a/app/views/shared/dossiers/editable_champs/_titre_identite.html.haml b/app/views/shared/dossiers/editable_champs/_titre_identite.html.haml index 0fc1f99c8..e4e65d20d 100644 --- a/app/views/shared/dossiers/editable_champs/_titre_identite.html.haml +++ b/app/views/shared/dossiers/editable_champs/_titre_identite.html.haml @@ -1,4 +1,4 @@ = render 'shared/attachment/edit', { form: form, attached_file: champ.piece_justificative_file, - template: champ.type_de_champ.piece_justificative_template, user_can_destroy: true } + user_can_destroy: true } diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 2784f955f..9e06230bf 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -6,6 +6,7 @@ Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer ActiveSupport.on_load(:active_storage_blob) do include BlobSignedIdConcern include BlobVirusScannerConcern + include BlobTitreIdentiteWatermarkConcern end # When an OpenStack service is initialized it makes a request to fetch