From 354fddde96494f25780d6692414f3893eac2ff23 Mon Sep 17 00:00:00 2001 From: Anderson Macedo Date: Sun, 28 Jul 2024 15:05:11 -0300 Subject: [PATCH] Initial work for generating images with transparent background --- app/controllers/calendars_controller.rb | 3 +- app/controllers/concerns/user_scoped.rb | 13 ++++ .../images/calendars_controller.rb | 17 +++++ app/controllers/signatures_controller.rb | 10 +-- app/controllers/timelines_controller.rb | 3 +- app/models/user.rb | 1 + app/models/user/calendar_imageable.rb | 23 ++++++ app/models/user/calendar_images.rb | 73 +++++++++++++++++++ app/views/users/signature.html.erb | 12 ++- config/routes.rb | 4 + 10 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 app/controllers/concerns/user_scoped.rb create mode 100644 app/controllers/images/calendars_controller.rb create mode 100644 app/models/user/calendar_imageable.rb create mode 100644 app/models/user/calendar_images.rb diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index fde3dc28..85c9ac25 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -1,6 +1,7 @@ class CalendarsController < ApplicationController + include UserScoped + def show - @user = User.find_by!(username: params[:user_username]) @calendar = @user.calendars.fetch(params[:year]) fresh_when [@user, @calendar] diff --git a/app/controllers/concerns/user_scoped.rb b/app/controllers/concerns/user_scoped.rb new file mode 100644 index 00000000..87938c4b --- /dev/null +++ b/app/controllers/concerns/user_scoped.rb @@ -0,0 +1,13 @@ +module UserScoped + extend ActiveSupport::Concern + + included do + before_action :set_user + end + + private + + def set_user + @user = User.find_by!(username: params[:user_username]) + end +end diff --git a/app/controllers/images/calendars_controller.rb b/app/controllers/images/calendars_controller.rb new file mode 100644 index 00000000..879a8b3d --- /dev/null +++ b/app/controllers/images/calendars_controller.rb @@ -0,0 +1,17 @@ +class Images::CalendarsController < ApplicationController + include UserScoped + + def show + if small? + redirect_to @user.calendar_image.variant(:small) + else + redirect_to @user.calendar_image.variant(:large) + end + end + + private + + def small? + params[:variant] == "small" + end +end diff --git a/app/controllers/signatures_controller.rb b/app/controllers/signatures_controller.rb index 993cdbdb..08b0d789 100644 --- a/app/controllers/signatures_controller.rb +++ b/app/controllers/signatures_controller.rb @@ -1,13 +1,7 @@ class SignaturesController < ApplicationController - before_action :set_user + include UserScoped def show - redirect_to url_for(@user.signature) - end - - private - - def set_user - @user = User.with_attached_signature.find_by!(username: params[:user_username]) + redirect_to @user.signature end end diff --git a/app/controllers/timelines_controller.rb b/app/controllers/timelines_controller.rb index e8b9ee56..abe494e3 100644 --- a/app/controllers/timelines_controller.rb +++ b/app/controllers/timelines_controller.rb @@ -1,6 +1,7 @@ class TimelinesController < ApplicationController + include UserScoped + def show - @user = User.find_by!(username: params[:user_username]) @calendar = @user.calendars.fetch(params[:year]) fresh_when [@user, @calendar] diff --git a/app/models/user.rb b/app/models/user.rb index 6c6c6b51..0c2f00e6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,6 +3,7 @@ class User < ApplicationRecord include MALSyncable include Mergeable include Calendars + include CalendarImageable include Signaturable include Deactivatable include Geolocatable diff --git a/app/models/user/calendar_imageable.rb b/app/models/user/calendar_imageable.rb new file mode 100644 index 00000000..386b07bb --- /dev/null +++ b/app/models/user/calendar_imageable.rb @@ -0,0 +1,23 @@ +module User::CalendarImageable + extend ActiveSupport::Concern + + included do + has_one_attached :calendar_image do |attachable| + attachable.variant :large, resize_to_limit: [1200, 180], preprocessed: true + attachable.variant :small, resize_to_limit: [600, 150], preprocessed: true + end + + after_create_commit :enqueue_calendar_images_generation + after_update_commit :enqueue_calendar_images_generation, if: -> { calendar_images.obsolete? } + end + + def calendar_images + User::CalendarImages.new(self) + end + + private + + def enqueue_calendar_images_generation + calendar_images.generate_later + end +end diff --git a/app/models/user/calendar_images.rb b/app/models/user/calendar_images.rb new file mode 100644 index 00000000..9c090191 --- /dev/null +++ b/app/models/user/calendar_images.rb @@ -0,0 +1,73 @@ +require "image_processing/vips" + +class User::CalendarImages + def initialize(user) + super() + @user = user + end + + def generate + Instrumentation.instrument(title: "#{self.class.name}#generate") do + user.with_time_zone { generate_from_activities } + end + end + + def generate_later + User::CalendarImages::GenerateJob.perform_later(user) + end + + def obsolete? + return true unless user.calendar_image.attached? + return true if user.saved_change_to_checksum? + + user.calendar_image.blob.created_at.in_time_zone.to_date != Time.zone.today + end + + class GenerateJob < ApplicationJob + retry_on(*BrowserSession::RETRYABLE_ERRORS, attempts: 10, wait: :polynomially_longer) + + limits_concurrency to: 1, key: :screenshots, duration: 2.hours + + queue_as :screenshots + + def perform(user) + user.calendar_images.generate + end + end + + private + + attr_reader :user + + def generate_from_activities + calendar_html = render_calendar_html + + capture_html_screenshot(calendar_html) + end + + def render_calendar_html + activities_amount_per_day = user.calendars.current_year.activities_amount_sum_per_day + + ApplicationController.render("users/signature", locals: { activities_amount_per_day: }, layout: nil) + end + + def capture_html_screenshot(html_page) + BrowserSession.fetch_page do |page| + page.go_to("data:text/html;base64,#{Base64.strict_encode64(html_page)}") + + image = page + .screenshot( + encoding: :binary, + selector: ".signature", + format: :png, + background_color: Ferrum::RGBA.new(0, 0, 0, 0.0) + ) + + user.calendar_image.attach( + io: StringIO.new(image), + filename: "#{user.username}_calendar.png", + content_type: "application/png" + ) + end + end +end diff --git a/app/views/users/signature.html.erb b/app/views/users/signature.html.erb index 06e3f578..8cad8a2a 100644 --- a/app/views/users/signature.html.erb +++ b/app/views/users/signature.html.erb @@ -3,10 +3,16 @@ diff --git a/config/routes.rb b/config/routes.rb index 3a1e414d..a26fea94 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,10 @@ resource :signature, only: :show, on: :member resources :calendars, only: :show, on: :member, param: "year" resources :timelines, only: :show, on: :member, param: "year" + + namespace :images do + resource :calendar, on: :member, only: :show + end end get "/about", to: "application#about", as: "about"