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 000000000..07114bdcf Binary files /dev/null and b/app/assets/images/watermark.png differ 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