记一次用rmagick绘制图像
最近做了个需求,大概要求是将几个元素(分享人头像,小程序码,商户名称,商户头像,背景图片),根据页面配置的形式绘制出分享图片,大概效果如下:
为此引用rmagick的gem包,其中所需gem包有
gem 'rmagick' gem 'mini_magick'
写了个图片拼接工具类,其中代码中用到的 ContentAgent::Content 对象是我们公司自己封装的一个内容中心服务器,其主要用于图片的上传,下载
# encoding: utf-8 # magic image 合并图片工具 class MagicImageTools class << self # # == 根据configs合并为一个图片 # # @param [Array<Hash>] configs 配置 # def composite_by_configs(configs) configs = configs.deep_symbolize_keys container = configs.shift container_magic_img = container[:type] == 'blank' ? blank_container_magic_image(container[:options]) : prepare_image_for_composite(container) # 子项 configs.each do |config| next if config[:value].blank? log_info "现在的配置是 #{config}" case config[:type] when 'image' magic_img = prepare_image_for_composite(config) composite_images(container_magic_img, magic_img, config[:options]) when 'text' add_text(container_magic_img, config[:value], config[:options]) else log_info "composite_by_configs 子项的type#{config[:type]}不认识" end end cid = to_normal_image(container_magic_img) log_info "composite_by_configs cid: #{cid}" cid end def prepare_image_for_composite(config) options = config[:options] || {} case options[:image_type] when 'magic_image' magic_img = config[:value] when 'path' img = ContentAgent::Content.create_by_file(config[:value]).urid else img = config[:value] end magic_img = to_magic_image(img) unless magic_img.present? if !options[:skip_resize] options[:force_resize] ? force_resize_magic_image(magic_img, options[:width], options[:height]) : resize_magic_image(magic_img, options) end magic_img end # # == 合并图片, 在容器图container_img中添加img # # @param container_img 容器图 # @param img 图片 # def composite_images(container_img, img, options = {}) options.symbolize_keys! x = options[:x] || 0 y = options[:y] || 0 diameter = options[:width] || 103 radius = diameter / 2 # 此处可以传是否需要对图片做 方 转 圆型 if options[:radius] pr = Magick::Draw.new pr.define_clip_path('circle') { pr.circle radius, radius, radius, 0 } pr.push pr.clip_path('circle') pr.composite(0, 0, diameter, diameter, img) pr.pop # 这一步是画个跟目标图片同样大小对透明图片,这样就可以将裁剪后多余部分隐掉 canvas = Magick::Image.new(diameter, diameter) { |c| c.background_color = "Transparent" } pr.draw(canvas) # 以容器图片的左上角作为x, y轴的起点 container_img.composite!(canvas, x, y, Magick::OverCompositeOp) else container_img.composite!(img, x, y, Magick::OverCompositeOp) end end # 在container_img中添加文字, 默认以容器图片的左上角作为x, y轴的起点 def add_text(container_img, text, options = {}) options.symbolize_keys! width = options[:width] || 0 height = options[:height] || 0 x = options[:x] || 0 y = options[:y] || 0 # Magick::NorthWestGravity 表示图片的西北点(即左上角)作为x, y轴的起点 # Magick::CenterGravity 表示图片的中心点作为x, y轴的起点 gravity = options[:gravity] || Magick::NorthWestGravity font_size = options[:font_size] || 16 color = options[:color] || 'black' font = text_font copyright = Magick::Draw.new copyright.annotate(container_img, width, height, x, y, text) do # 每次写字之前设置stroke为'transparent', 防止之前设置stroke为其他值 self.stroke = 'transparent' self.gravity = gravity self.pointsize = font_size self.fill = color self.font = font end end # 将 图片content_id 转为 Magick::Image类型图片 def to_magic_image(url) path = Tools.download_img(url) magic_img = Magick::Image.read(path).first File.delete(path) if File.exist?(path) magic_img end # 将 Magick::Image类型图片 转为 图片content_id def to_normal_image(magic_img) path = "tmp/magic_img_#{Tools.fill_code}.png" magic_img.write(path) img = ContentAgent::Content.create_by_file(path) File.delete(path) if File.exist?(path) img.urid end # 等比例转换图片尺寸 def resize_magic_image(magic_img, options = {}) options.symbolize_keys! max_width = options[:max_width] || 100 max_height = options[:max_height] || 100 img_width, img_height = get_image_size(magic_img) if img_width*max_height > img_height*max_width width, height = [max_width, max_width*img_height/img_width] else width, height = [max_height*img_width/img_height, max_height] end force_resize_magic_image(magic_img, width, height) # 将图片的尺寸转换成为width*height end # 强制转换图片尺寸为width, height def force_resize_magic_image(magic_img, width, height) magic_img.resize!(width, height) # 将图片的尺寸转换成为width*height end # 获取图片的width, height def get_image_size(magic_img) width = magic_img.columns height = magic_img.rows [width, height] end # 创建空白的背景容器图片 def blank_container_magic_image(options = {}) options.symbolize_keys! width = options[:width] || 375 height = options[:height] || 667 bg_color = options[:bg_color] || 'white' bg_magic_color = Magick::HatchFill.new(bg_color) Magick::Image.new(width, height, bg_magic_color) end # '暂无图片' def no_image_magic_image Magick::Image.read('app/assets/images/common/common-no-image.png').first end # 字体 def text_font 'app/assets/fonts/simsun.ttc' end end end
工具使用侧
# 1.导购头像 # 2.小程序码 # 3.背景图 # 4.商户名称 def generate_shard_img(task, guider) bg_img_width = 750 bg_img_height = 1334 width_size = 375 qrcode_size = 105 avatar_size = 50 font_size = 24 retailer_name_width = 130 retailer_name_height = 200 img_location = task.img_location.as_smart guide_photo_x = img_location[:guide_photo_x] || 318 guide_photo_y = img_location[:guide_photo_y] || 1116 weapp_x = img_location[:weapp_x] || 532 weapp_y = img_location[:weapp_y] || 1080 logo_x = img_location[:logo_x] || 188 logo_y = img_location[:logo_y] || 22 retailer_name_x = img_location[:retailer_name_x] || 390 retailer_name_y = img_location[:retailer_name_y] || 20 bg_img = get_bg_img(task, bg_img_width, bg_img_height) weapp_url = get_weapp_url(task, guider) avatar = get_avatar(guider, avatar_size) retailer_logo = get_retailer_log retailer_name = get_retailer_name configs = [ { value: bg_img, type: "image", flag: "背景图片", options: { image_type: "magic_image", width: width_size, height: 620, skip_resize: true } }, { value: weapp_url, type: "image", flag: "小程序码", options: { image_type: "cid", force_resize: true, radius: true, width: qrcode_size, height: qrcode_size, x: weapp_x, y: weapp_y } }, { value: retailer_logo, flag: "商户logo", type: "image", options: { image_type: "cid", force_resize: true, width: 130, height: 130, x: logo_x, y: logo_y, gravity: Magick::CenterGravity } }, # 商品名称只显示前10个字 { value: (retailer_name.length > 10 ? (retailer_name[0..-4] + '...') : retailer_name), flag: "商户名称", type: "text", options: { font_size: font_size, skip_resize: true, width: retailer_name_width, height: retailer_name_height, x: retailer_name_x, y: retailer_name_y, gravity: Magick::CenterGravity } }, { value: avatar, flag: "导购头像", type: "image", options: { image_type: "cid", force_resize: true, radius: true, width: avatar_size, height: avatar_size, x: guide_photo_x, y: guide_photo_y } } ] MagicImageTools.composite_by_configs(configs) end