[RAILS] Try to imitate marshmallows with MiniMagick

Introduction

When sharing an article without an image of Qiita or Hatena Blog, I saw that an OGP image with a title came out, and I always wanted to imitate it. It was. When I saw the comment on Marshmallow as an image and attached to the tweet of my reply, I was impressed that it was good, and I wanted to imitate it, so I made it.

image.png

Sample application

The sample application is a simple one that allows users to post their favorite sentences. (Basically it's rails g scaffold post body: string.) I've done a lot to add an image to the tweets that share the post.

Source https://github.com/ken1flan/minimagick_ogp

heroku https://minimagick-ogp.herokuapp.com/posts

Details

Tweet button

I think there are two ways to share information using images on twitter.

--Tweet with an image attached --OGP preview of links

I thought that attaching the previous image was easy when tweeting normally, but I found that the common tweet button was just that it was not easy to attach an image, so I used OGP I decided to use the method.

erb:app/views/posts/show.html.erb


<!--abridgement-->
<p>
  <%= render 'common/twitter_share_button', url: post_url(@post), message: 'Thank you!' %>
</p>
<!--abridgement-->

erb:app/views/common/_twitter_share_button.html.erb


<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="<%= message %>" data-url="<%= url %>" data-show-count="false">Tweet</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Aspect ratio of Twitter card and image

You can choose the preview shape by setting twitter: card in the meta tag of the page to be shared. This makes it better to change the aspect ratio of the OGP image. Even if you combine images with embedded text, it will not be transmitted if it is cut off.

Here, I set summary_large_image to make the image larger, and set the size of the image to be combined to 640x315.

Sample uses meta-tags.

erb:app/views/posts/show.html.erb


<%
  set_meta_tags title: "post##{@post.id}", og: { title: "post##{@post.id}", description: @post.body, image: "#{request.base_url}/#{@post.image_path}" }, twitter: { card: 'summary_large_image' }
%>
  :
 (abridgement)

font

Since we are writing text to the image, we need the font at that time. You will inevitably distribute it, so you have to find the one provided by the license that you can distribute what you wrote in the image. The sample uses the IPAex font.

I put this in app/assets/fonts.

Class for image composition

I named it Post :: Image because it is a composite of images for postPost.

app/models/post/image.rb


class Post::Image
  # (abridgement)
  attr_reader :post

  def initialize(post)
    @post = post
  end
  # (abridgement)
end

If you set this to image_path from Post, you can get the path of the composited image.

app/models/post.rb


class Post < ApplicationRecord
  delegate :path, to: :image, prefix: true

  def image
    @image ||= Post::Image.new(self)
  end
end

Text formatting

Text drawing with MiniMagick does not have functions such as line breaks at specified positions, so you need to format it yourself.

Since the size of the image is finite, decide the number of characters per line that can be displayed and the number of lines, and format the text accordingly.

app/models/post/image.rb


class Post::Image
  # (abridgement)
  MAX_ROWS = 5
  COLS = 20
  ROWS = 10
  OMMIT_MESSAGE = '… (Please omit it.)'
  # (abridgement)
  def formated_body
    lines = post.body.lines.map { |line| line.scan(/.{1,#{COLS}}/) }.flatten
    lines = lines[0, MAX_ROWS - 1].push(OMMIT_MESSAGE) if lines.size > MAX_ROWS
    lines.join('\n')
  end
  # (abridgement)
end

Synthetic

I open the image and draw the formatted text.

app/models/post/image.rb


class Post::Image
  FRAME_IMAGE_PATH = Rails.root.join('app/assets/images/flame.png')
  FONT_PATH = Rails.root.join('app/assets/fonts/ipaexg00401/ipaexg.ttf')
  FONT_SIZE = 25
  INTERLINE_SPACING = (FONT_SIZE * 0.5).round
  COLOR_CODE = '#252828'
  START_X = 65
  START_Y = 60
  # (abridgement)
  def image
    image = MiniMagick::Image.open(FRAME_IMAGE_PATH) #Open the frame image
    image.combine_options do |c|
      c.gravity 'northwest'                 #From top left
      c.pointsize FONT_SIZE                 #With the specified font size
      c.font FONT_PATH                      #With the specified font
      c.interline_spacing INTERLINE_SPACING #With the space between the specified lines
      c.stroke COLOR_CODE                   #In the specified color
      c.annotate "+#{START_X}+#{START_Y},0", formated_body #Draws the formatted text from the specified position
    end
  end
  # (abridgement)
end

Reset Tweet Preview

I think there are times when you don't like the image you made. However, it seems that it is related to optimization, but sometimes the preview is cached and changes are not reflected. At that time, if you look at the preview with the card validator, it will be retaken.

https://cards-dev.twitter.com/validator

Try it

When using OGP, the bottleneck is that the content to be shared must have a URL in the first place. I want to divide the content neatly on a regular basis.

Recommended Posts

Try to imitate marshmallows with MiniMagick
Try to imitate the idea of a two-dimensional array with a one-dimensional array
Try to implement login function with Spring-Boot
Try to link Ruby and Java with Dapr
Try to implement login function with Spring Boot
Try to get redmine API key with ruby
Try to implement TCP / IP + NIO with JAVA
Try to automate migration with Spring Boot Flyway
Try to display hello world with spring + gradle
Try to summarize the common layout with rails
Try DI with Micronaut
I tried what I wanted to try with Stream softly.
Try create with Trailblazer
Try to release gem
Try WebSocket with jooby
Feel free to try Elasticsearch cluster with WSL2 + Docker
Try WildFly with Docker
Try connecting to AzureCosmosDB Emulator for Docker with Java
Try to work with Keycloak using Spring Security SAML (Spring 5)
[Beginner] Try to make a simple RPG game with Java ①
[swift5] Try to make an API client with various methods
Try Spring Boot from 0 to 100.
Try using GloVe with Deeplearning4j
Java to play with Function
Try using view_component with rails
How to number (number) with html.erb
How to update with activerecord-import
Connect to DB with Java
Connect to MySQL 8 with Java
Try reading XML with JDOM
Connect to oracle with eclipse!
Try to speed up Java console program startup with GraalVM native-image
Try to make a cross-platform application with JRuby (jar file generation)
Run logstash with Docker and try uploading data to Elastic Cloud