feat: enhance image handling for weather arts

- Remove deprecated `image_with_watermark` attachment.
- Introduce `webp_image`, `preview_image`, and `watermarked_image` methods in `WeatherArt` model for optimized image formats.
- Update views to use new image variants, including webp and previews, improving loading times and visual quality.
- Ensure images are processed with relevant attributes such as quality and dimensions for better performance and user experience.

These changes enhance the image handling capabilities of the application by ensuring images are served in a more efficient format (WebP) and with improved resizing options, leading to better performance overall.
This commit is contained in:
songtianlun 2025-02-13 15:25:12 +08:00
parent 5efe441fa8
commit 5ae0367525
7 changed files with 153 additions and 13 deletions

View File

@ -42,14 +42,14 @@ class City < ApplicationRecord
# 根据数据库类型构建不同的查询 # 根据数据库类型构建不同的查询
base_query = if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite" base_query = if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
joins(<<-SQL.squish) joins(<<-SQL.squish)
LEFT JOIN ahoy_events ON LEFT JOIN ahoy_events ON#{' '}
json_extract(ahoy_events.properties, '$.city_id') = cities.id json_extract(ahoy_events.properties, '$.city_id') = cities.id
AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view' AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view'
AND ahoy_events.time > '#{start_time}' AND ahoy_events.time > '#{start_time}'
SQL SQL
else else
joins(<<-SQL.squish) joins(<<-SQL.squish)
LEFT JOIN ahoy_events ON LEFT JOIN ahoy_events ON#{' '}
(ahoy_events.properties::jsonb->>'city_id')::integer = cities.id (ahoy_events.properties::jsonb->>'city_id')::integer = cities.id
AND ahoy_events.properties::jsonb->>'event_type' = 'city_view' AND ahoy_events.properties::jsonb->>'event_type' = 'city_view'
AND ahoy_events.time > '#{start_time}' AND ahoy_events.time > '#{start_time}'

View File

@ -4,7 +4,6 @@ class WeatherArt < ApplicationRecord
belongs_to :city belongs_to :city
has_one_attached :image has_one_attached :image
has_one_attached :image_with_watermark
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id
has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id
@ -59,4 +58,145 @@ class WeatherArt < ApplicationRecord
def image_url def image_url
image.attached? ? image.blob : nil image.attached? ? image.blob : nil
end end
def webp_image
return nil unless image.attached?
image.variant(
format: "webp",
saver: {
quality: 100,
strip: true, # 移除元数据以减小文件大小
interlace: "plane" # 渐进式加载
}
)
end
# 添加图片变体处理
PREVIEW_DIMENSIONS = {
big: [ 1792, 1024 ],
medium: [ 896, 512 ],
small: [ 448, 256 ]
}.freeze
def preview_image(size = :medium)
return nil unless image.attached?
width, height = PREVIEW_DIMENSIONS[size] || PREVIEW_DIMENSIONS[:medium]
image.variant(
resize_to_limit: [ width, height ],
format: "webp",
saver: {
quality: 75,
strip: true, # 移除元数据以减小文件大小
interlace: "plane" # 渐进式加载
}
)
end
def watermarked_image
return nil unless image.attached?
overlay_text = create_overlay_text
image.variant(
composite: [ {
input: overlay_text,
gravity: "southeast"
} ]
)
end
private
def create_overlay_text
{
create: {
width: 400,
height: 100,
background: [ 0, 0, 0, 0.5 ] # 半透明黑色背景
},
"svg-overlay": %(
<svg width="400" height="100">
<text x="20" y="40"
style="fill: white; font-family: Arial; font-size: 20px;">
#{city.name} - #{weather_date.strftime('%Y-%m-%d')}
</text>
<text x="20" y="70"
style="fill: white; font-family: Arial; font-size: 20px;">
© todayaiweather.com
</text>
</svg>
)
}
end
def create_text_layer(font_size)
text = [
weather_date.strftime("%Y-%m-%d"),
"#{temperature}°C, #{description}",
"#{city.name}, #{city.country.name}, #{city.country.region.name}",
"© todayaiweather.com"
].join("\n")
{
create: {
width: 600,
height: 200,
background: [ 0, 0, 0, 0 ]
},
"svg-overlay": %(
<svg width="600" height="200">
<style>
.text {
font-family: Arial, sans-serif;
font-size: #{font_size}px;
}
.shadow {
fill: white;
stroke: black;
stroke-width: 2px;
paint-order: stroke fill;
}
</style>
<text x="20" y="#{font_size + 10}" class="text shadow">#{weather_date.strftime('%Y-%m-%d')}</text>
<text x="20" y="#{font_size * 2 + 20}" class="text shadow">#{temperature}°C, #{description}</text>
<text x="20" y="#{font_size * 3 + 30}" class="text shadow">#{city.name}, #{city.country.name}</text>
<text x="20" y="#{font_size * 4 + 40}" class="text shadow">© todayaiweather.com</text>
</svg>
)
}
end
def watermark_command(font_size:, stroke_width:, spacing:)
date_str = weather_date.strftime("%Y-%m-%d")
weather_info = "#{temperature}°C, #{description}"
location_info = "#{city.name}, #{city.country.name}, #{city.country.region.name}"
copyright = "© todayaiweather.com"
"gravity southeast " \
"fill white " \
"font Arial " \
"pointsize #{font_size} " \
"stroke black " \
"strokewidth #{stroke_width} " \
"text 30,#{spacing * 12} '#{copyright}' " \
"text 30,#{spacing * 8} '#{location_info}' " \
"text 30,#{spacing * 4} '#{weather_info}' " \
"text 30,#{spacing} '#{date_str}'"
end
def watermark_text
date_str = weather_date.strftime("%Y-%m-%d")
weather_info = "#{temperature}°C, #{description}"
location_info = "#{city.name}, #{city.country.name}, #{city.country.region.name}"
copyright = "© todayaiweather.com"
[
"text 30,120 '#{copyright}'",
"text 30,80 '#{location_info}'",
"text 30,40 '#{weather_info}'",
"text 30,0 '#{date_str}'"
].join(" ")
end
end end

View File

@ -6,7 +6,7 @@
<!-- 背景图像 --> <!-- 背景图像 -->
<% if featured_art&.image&.attached? %> <% if featured_art&.image&.attached? %>
<div class="absolute inset-0 h-[40vh] overflow-hidden"> <div class="absolute inset-0 h-[40vh] overflow-hidden">
<%= image_tag featured_art.image, <%= image_tag featured_art.webp_image.processed,
class: "w-full h-full object-cover" %> class: "w-full h-full object-cover" %>
<div class="absolute inset-0 bg-gradient-to-b from-base-100/30 via-base-100/60 to-base-100"></div> <div class="absolute inset-0 bg-gradient-to-b from-base-100/30 via-base-100/60 to-base-100"></div>
</div> </div>
@ -107,7 +107,7 @@
<div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 group overflow-hidden"> <div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 group overflow-hidden">
<figure class="relative aspect-square overflow-hidden"> <figure class="relative aspect-square overflow-hidden">
<% if art.image.attached? %> <% if art.image.attached? %>
<%= image_tag art.image, <%= image_tag art.preview_image.processed,
class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %> class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %>
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div class="absolute inset-0 bg-gradient-to-t from-black via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>

View File

@ -5,7 +5,7 @@
<div class="relative"> <div class="relative">
<!-- 图片 --> <!-- 图片 -->
<figure class="aspect-[16/9] overflow-hidden"> <figure class="aspect-[16/9] overflow-hidden">
<%= image_tag city.latest_weather_art.image, <%= image_tag city.latest_weather_art.preview_image.processed,
class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %> class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %>
</figure> </figure>

View File

@ -6,7 +6,7 @@
<!-- 背景图像和渐变 --> <!-- 背景图像和渐变 -->
<% if featured_art&.image&.attached? %> <% if featured_art&.image&.attached? %>
<div class="absolute inset-0 h-[60vh] overflow-hidden"> <div class="absolute inset-0 h-[60vh] overflow-hidden">
<%= image_tag featured_art.image, <%= image_tag featured_art.webp_image.processed,
class: "w-full h-full object-cover object-center" %> class: "w-full h-full object-cover object-center" %>
<div class="absolute inset-0 bg-gradient-to-b from-base-100/40 via-base-100/80 to-base-100"></div> <div class="absolute inset-0 bg-gradient-to-b from-base-100/40 via-base-100/80 to-base-100"></div>
</div> </div>

View File

@ -2,7 +2,7 @@
<!-- 背景效果 --> <!-- 背景效果 -->
<% if @city.latest_weather_art&.image&.attached? %> <% if @city.latest_weather_art&.image&.attached? %>
<div class="fixed inset-0 -z-10"> <div class="fixed inset-0 -z-10">
<%= image_tag @city.latest_weather_art.image, <%= image_tag @city.latest_weather_art.webp_image.processed,
class: "absolute w-full h-full object-cover scale-110 filter blur-2xl opacity-25" %> class: "absolute w-full h-full object-cover scale-110 filter blur-2xl opacity-25" %>
<div class="absolute inset-0 bg-gradient-to-b from-base-200/90 to-base-200/70 backdrop-blur-md"></div> <div class="absolute inset-0 bg-gradient-to-b from-base-200/90 to-base-200/70 backdrop-blur-md"></div>
</div> </div>
@ -103,7 +103,7 @@
<div class="card bg-base-100/80 backdrop-blur-sm shadow-xl hover:shadow-2xl transition-all duration-300 hover:-translate-y-1"> <div class="card bg-base-100/80 backdrop-blur-sm shadow-xl hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
<figure class="relative aspect-video overflow-hidden"> <figure class="relative aspect-video overflow-hidden">
<% if art.image.attached? %> <% if art.image.attached? %>
<%= image_tag art.image, <%= image_tag art.preview_image.processed,
class: "w-full h-full object-cover transform hover:scale-105 transition-transform duration-500" %> class: "w-full h-full object-cover transform hover:scale-105 transition-transform duration-500" %>
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent"> <div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent">
<div class="flex items-center justify-between text-white"> <div class="flex items-center justify-between text-white">

View File

@ -27,15 +27,15 @@
<figure class="relative lg:h-[30rem] h-auto overflow-hidden rounded-lg"> <!-- 添加圆角 --> <figure class="relative lg:h-[30rem] h-auto overflow-hidden rounded-lg"> <!-- 添加圆角 -->
<div class="gallery" data-controller="photo-swipe-lightbox"> <div class="gallery" data-controller="photo-swipe-lightbox">
<div data-photo-swipe-lightbox-target="gallery" class="h-full"> <div data-photo-swipe-lightbox-target="gallery" class="h-full">
<% blob = @weather_art.image_blob %> <% watermarked = @weather_art.webp_image.processed %>
<%= link_to rails_blob_path(blob), <%= link_to rails_blob_path(watermarked),
data: { data: {
pswp_src: rails_blob_url(blob), pswp_src: rails_blob_url(watermarked),
pswp_caption: 'Weather Art', pswp_caption: 'Weather Art',
pswp_width: 1792, pswp_width: 1792,
pswp_height: 1024 pswp_height: 1024
} do %> } do %>
<%= image_tag @weather_art.image, class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %> <%= image_tag @weather_art.preview_image(:big).processed, class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %>
<%#= image_tag @weather_art.watermarked_variant.processed , class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %> <%#= image_tag @weather_art.watermarked_variant.processed , class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %>
<% end %> <% end %>
</div> </div>