From bbf8dfc2e6fba66cc5b288e61a223a09591eeaaa Mon Sep 17 00:00:00 2001 From: songtianlun Date: Wed, 29 Jan 2025 01:53:50 +0800 Subject: [PATCH] feat: add watermarking functionality to weather art - Include the image_processing gem to handle image manipulation - Create a background worker for adding watermarks to weather art images - Update WeatherArt model to attach watermark images - Add a new watermark image asset This commit enhances the WeatherArt feature by allowing images to have watermarks added asynchronously, improving the visual presentation of the art. It ensures sufficient image dimensions before processing and includes error handling for the worker. --- Gemfile | 2 + Gemfile.lock | 9 +++ .../today_ai_weather_copyright_watermark1.png | Bin 0 -> 6033 bytes app/models/weather_art.rb | 1 + .../add_watermark_to_weatherart_worker.rb | 53 ++++++++++++++++++ 5 files changed, 65 insertions(+) create mode 100644 app/assets/images/today_ai_weather_copyright_watermark1.png create mode 100644 app/workers/add_watermark_to_weatherart_worker.rb diff --git a/Gemfile b/Gemfile index 1d6c1bc..b75cd2b 100644 --- a/Gemfile +++ b/Gemfile @@ -60,6 +60,8 @@ gem "aws-sdk-s3", "~> 1.177" gem "sidekiq", "~> 7.3" gem "sidekiq-scheduler", "~> 5.0" +gem "image_processing", "~> 1.13" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" diff --git a/Gemfile.lock b/Gemfile.lock index 30d3377..9ff2d31 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -164,6 +164,7 @@ GEM multipart-post (~> 2.0) faraday-net_http (3.4.0) net-http (>= 0.5.0) + ffi (1.17.1-arm64-darwin) formtastic (5.0.0) actionpack (>= 6.0.0) formtastic_i18n (0.7.0) @@ -183,6 +184,9 @@ GEM multi_xml (>= 0.5.2) i18n (1.14.6) concurrent-ruby (~> 1.0) + image_processing (1.13.0) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) inherited_resources (1.14.0) actionpack (>= 6.0) has_scope (>= 0.6) @@ -240,6 +244,7 @@ GEM matrix (0.4.2) meta-tags (2.22.1) actionpack (>= 6.0.0, < 8.1) + mini_magick (4.13.2) mini_mime (1.1.5) minitest (5.25.4) msgpack (1.7.5) @@ -386,6 +391,9 @@ GEM faraday (>= 1) faraday-multipart (>= 1) ruby-progressbar (1.13.0) + ruby-vips (2.2.2) + ffi (~> 1.12) + logger ruby2_keywords (0.0.5) rubyzip (2.4.1) rufus-scheduler (3.9.2) @@ -502,6 +510,7 @@ DEPENDENCIES down (~> 5.4) friendly_id (~> 5.5) httparty (~> 0.22.0) + image_processing (~> 1.13) jbuilder jsbundling-rails kamal diff --git a/app/assets/images/today_ai_weather_copyright_watermark1.png b/app/assets/images/today_ai_weather_copyright_watermark1.png new file mode 100644 index 0000000000000000000000000000000000000000..47c2ce959346723e24fb1d0f93de116a043a02a7 GIT binary patch literal 6033 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 e + Rails.logger.error "Error adding watermark to WeatherArt #{weather_art_id}: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + end + + private + + attr_reader :weather_art + + def add_watermark + return if weather_art.image_with_watermark.attached? + + watermark_path = Rails.root.join("app/assets/images/today_ai_weather_copyright_watermark1.png") + return unless File.exist?(watermark_path) + + image_tempfile = nil + watermark_tempfile = nil + begin + image_tempfile = weather_art.image.download + return unless image_dimensions_are_sufficient?(image_tempfile.path) + + image = ImageProcessing::Vips.source(image_tempfile.path) + watermark = ImageProcessing::Vips.source(watermark_path) + watermarked_image = image.composite(watermark, "overlay") + watermark_tempfile = Tempfile.new([ "watermarked_image", ".png" ]) + watermarked_image.write_to_file(watermark_tempfile.path) + weather_art.image_with_watermark.attach( + io: File.open(watermark_tempfile.path), + filename: "#{generate_filename("watermarked")}", + content_type: "image/png" + ) + ensure + watermark_tempfile.unlink if watermark_tempfile + end + end + + def image_dimensions_are_sufficient?(image_path) + dimensions = ImageProcessing::Vips.source(image_path).sizes + dimensions.width >= 200 && dimensions.height >= 200 + end + + def generate_filename(prefix) + "#{prefix}_#{weather_art.image.filename.base}" + end +end