This is where the bulk of the work is. This service renders out the preview images bit by bit. Currently we render the party name, creator, job icon, and weapon grid. This includes signatures and some fonts.
173 lines
5.1 KiB
Ruby
173 lines
5.1 KiB
Ruby
# app/services/canvas.rb
|
|
module PreviewService
|
|
class Canvas
|
|
PREVIEW_WIDTH = 1200
|
|
PREVIEW_HEIGHT = 630
|
|
DEFAULT_BACKGROUND_COLOR = '#1a1b1e'
|
|
|
|
# Padding and spacing constants
|
|
PADDING = 24
|
|
TITLE_IMAGE_GAP = 24
|
|
GRID_GAP = 4
|
|
|
|
def initialize(image_fetcher)
|
|
@image_fetcher = image_fetcher
|
|
end
|
|
|
|
def create_blank_canvas(width: PREVIEW_WIDTH, height: PREVIEW_HEIGHT, color: DEFAULT_BACKGROUND_COLOR)
|
|
temp_file = Tempfile.new(%w[canvas .png])
|
|
|
|
MiniMagick::Tool::Convert.new do |convert|
|
|
convert.size "#{width}x#{height}"
|
|
convert << "xc:#{color}"
|
|
convert << temp_file.path
|
|
end
|
|
|
|
temp_file
|
|
end
|
|
|
|
def add_text(image, party_name, job_icon: nil, user: nil, **options)
|
|
font_size = options.fetch(:size, '32')
|
|
font_color = options.fetch(:color, 'white')
|
|
|
|
# Load custom font for username, for later use
|
|
@font_path ||= Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
|
|
|
# Measure party name text size
|
|
text_metrics = measure_text(party_name, font_size)
|
|
|
|
# Draw job icon if provided
|
|
image = draw_job_icon(image, job_icon) if job_icon
|
|
|
|
# Draw party name text
|
|
image = draw_party_name(image, party_name, text_metrics, job_icon, font_color, font_size)
|
|
|
|
# Compute vertical center of the party name text line
|
|
party_text_center_y = PADDING + (text_metrics[:height] / 2.0)
|
|
|
|
# Draw user info if provided
|
|
image = draw_user_info(image, user, party_text_center_y, font_color) if user
|
|
|
|
{
|
|
image: image,
|
|
text_bottom_y: PADDING + text_metrics[:height] + TITLE_IMAGE_GAP
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def draw_job_icon(image, job_icon)
|
|
job_icon.format("png32")
|
|
job_icon.alpha('set')
|
|
job_icon.background('none')
|
|
job_icon.combine_options do |c|
|
|
c.filter "Lanczos" # High-quality filter
|
|
c.resize "64x64"
|
|
end
|
|
image = image.composite(job_icon) do |c|
|
|
c.compose "Over"
|
|
c.geometry "+#{PADDING}+#{PADDING}"
|
|
end
|
|
image
|
|
end
|
|
|
|
def draw_party_name(image, party_name, text_metrics, job_icon, font_color, font_size)
|
|
# Determine x position based on presence of job_icon
|
|
text_x = job_icon ? PADDING + 64 + 16 : PADDING
|
|
text_y = PADDING + text_metrics[:height]
|
|
|
|
image.combine_options do |c|
|
|
c.font @font_path
|
|
c.fill font_color
|
|
c.pointsize font_size
|
|
c.draw "text #{text_x},#{text_y} '#{party_name}'"
|
|
end
|
|
image
|
|
end
|
|
|
|
def draw_user_info(image, user, party_text_center_y, font_color)
|
|
username_font_size = 24
|
|
username_font_path = @font_path
|
|
|
|
# Fetch and prepare user picture
|
|
user_picture = @image_fetcher.fetch_user_picture(user.picture)
|
|
if user_picture
|
|
user_picture.format("png32")
|
|
user_picture.alpha('set')
|
|
user_picture.background('none')
|
|
user_picture.combine_options do |c|
|
|
c.filter "Lanczos" # Use a high-quality filter
|
|
c.resize "48x48"
|
|
end
|
|
end
|
|
|
|
# Measure username text size
|
|
username_metrics = measure_text(user.username, username_font_size, font: username_font_path)
|
|
|
|
right_padding = PADDING
|
|
total_user_width = 48 + 8 + username_metrics[:width]
|
|
user_x = image.width - right_padding - total_user_width
|
|
|
|
# Center user picture vertically relative to party text line
|
|
user_pic_y = (party_text_center_y - (48 / 2.0)).round
|
|
|
|
image = image.composite(user_picture) do |c|
|
|
c.compose "Over"
|
|
c.geometry "+#{user_x}+#{user_pic_y}"
|
|
end if user_picture
|
|
|
|
# Adjust text y-coordinate to better align vertically with the picture
|
|
# You may need to tweak the offset value based on visual inspection.
|
|
vertical_offset = 6 # Adjust this value as needed
|
|
user_text_y = (party_text_center_y + (username_metrics[:height] / 2.0) - vertical_offset).round
|
|
|
|
image.combine_options do |c|
|
|
c.font username_font_path
|
|
c.fill font_color
|
|
c.pointsize username_font_size
|
|
text_x = user_x + 48 + 12
|
|
c.draw "text #{text_x},#{user_text_y} '#{user.username}'"
|
|
end
|
|
|
|
image
|
|
end
|
|
|
|
def measure_text(text, font_size, font: 'Arial')
|
|
|
|
# Create a temporary file for the text measurement
|
|
temp_file = Tempfile.new(['text_measure', '.png'])
|
|
|
|
begin
|
|
# Use ImageMagick command to create an image with the text
|
|
command = [
|
|
'magick',
|
|
'-background', 'transparent',
|
|
'-fill', 'black',
|
|
'-font', font,
|
|
'-pointsize', font_size.to_s,
|
|
"label:#{text}",
|
|
temp_file.path
|
|
]
|
|
|
|
# Execute the command
|
|
system(*command)
|
|
|
|
# Use MiniMagick to read the image and get dimensions
|
|
image = MiniMagick::Image.open(temp_file.path)
|
|
|
|
{
|
|
height: image.height,
|
|
width: image.width
|
|
}
|
|
ensure
|
|
# Close and unlink the temporary file
|
|
temp_file.close
|
|
temp_file.unlink
|
|
end
|
|
rescue => e
|
|
Rails.logger.error "Text measurement error: #{e.message}"
|
|
# Fallback dimensions
|
|
{ height: 50, width: 200 }
|
|
end
|
|
end
|
|
end
|