diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index 7ba3f2208b7..5f3f9fe963c 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -32,6 +32,8 @@ def create @article.user = current_user if @article.user.nil? set_wip if @article.save + fit_to_size_for_ogp + redirect_to redirect_url(@article), notice: notice_message(@article) else render :new @@ -41,6 +43,8 @@ def create def update set_wip if @article.update(article_params) + fit_to_size_for_ogp + redirect_to redirect_url(@article), notice: notice_message(@article) else render :edit @@ -97,4 +101,11 @@ def notice_message(article) article.wip? ? '記事をWIPとして保存しました' : '記事を更新しました' end end + + def fit_to_size_for_ogp + return unless @article.thumbnail.attached? + + image_resizer = ImageResizer.new(@article.thumbnail) + image_resizer.fit_to!(*ImageResizer::OGP_SIZE) + end end diff --git a/app/models/article.rb b/app/models/article.rb index b84f49aa49d..f58d0c0e360 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -4,7 +4,6 @@ class Article < ApplicationRecord belongs_to :user include ActionView::Helpers::AssetUrlHelper - THUMBNAIL_SIZE = '1200x630>' has_one_attached :thumbnail before_validation :set_published_at, if: :will_be_published? @@ -21,7 +20,7 @@ class Article < ApplicationRecord def thumbnail_url if thumbnail.attached? - thumbnail.variant(resize: THUMBNAIL_SIZE).processed.url + thumbnail.blob.url else image_url('/images/articles/thumbnails/default.png') end diff --git a/app/models/image_resizer.rb b/app/models/image_resizer.rb new file mode 100644 index 00000000000..73dca38fa84 --- /dev/null +++ b/app/models/image_resizer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class ImageResizer + OGP_SIZE = [1200, 630].freeze + + def initialize(attached_one) + @attached_one = attached_one + @transformation = ImageResizerTransformation.new + end + + def fit_to!(width, height) + return nil if just_fit_to_size?(width, height) + + original_width, original_height = original_size + + fitted = @attached_one.variant(**@transformation.fit_to(original_width, original_height, width, height)) + .processed + .image.blob + + # 変形前の画像関連データの削除にpurge、purge_laterは不要。 + # データの削除はattachで担える + @attached_one.attach(fitted) + end + + def just_fit_to_size?(width, height) + original_width, original_height = original_size + width == original_width && height == original_height + end + + private + + def original_size + blob = @attached_one.blob + blob.analyze unless blob.analyzed? + [blob.metadata[:width], blob.metadata[:height]] + end +end diff --git a/app/models/image_resizer_transformation.rb b/app/models/image_resizer_transformation.rb new file mode 100644 index 00000000000..2a304f85f56 --- /dev/null +++ b/app/models/image_resizer_transformation.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class ImageResizerTransformation + def fit_to(original_width, original_height, width, height) + # ハッシュを返すが処理順(リサイズ->切り抜き)にオプションを記述 + {}.merge(resize(original_width, original_height, width, height), + center_crop(width, height)) + end + + # 縦横比を維持しながら、リサイズ後の縦をx、横をyとしたときに + # いずれかの条件を満たすようにリサイズする + # x = widht かつ y >= height + # x >= width かつ y = height + def resize(original_width, original_height, width, height) + ratio_w = width.to_f / original_width + ratio_h = height.to_f / original_height + + if ratio_w > ratio_h + { resize: "#{width}x" } + else + { resize: "x#{height}" } + end + end + + def center_crop(width, height) + { gravity: :center, + # 仮想キャンバスのメタデータをリセットするために!を末尾につける + # +repageは指定できないため、+repage相当の動作をする!を採用 + crop: "#{width}x#{height}+0+0!" } + end +end diff --git a/app/views/articles/_form.html.slim b/app/views/articles/_form.html.slim index f8e44c94a9e..9874fc8d4fb 100644 --- a/app/views/articles/_form.html.slim +++ b/app/views/articles/_form.html.slim @@ -57,7 +57,7 @@ .a-form-help p | ここにアップロードした画像が、X(Twitter)、Facebook で投稿した際に表示される画像として使われます。 - | 画像サイズは必ず 1200px × 630xpx でお願いします。 + | 画像サイズが 1200px x 630px でない場合は、登録時に自動で 1200px x 630px に加工されます。 - if params[:action] == 'edit' .form-item diff --git a/test/fixtures/active_storage/attachments.yml b/test/fixtures/active_storage/attachments.yml index fe637dcb6e5..d27458d4c07 100644 --- a/test/fixtures/active_storage/attachments.yml +++ b/test/fixtures/active_storage/attachments.yml @@ -69,3 +69,8 @@ kimuramitai_profile_image: name: profile_image record: kimuramitai (User) blob: kimuramitai_profile_image_blob + +article4_thumbnail_image: + name: thumbnail + record: article4 (Article) + blob: article4_thumbnail_blob diff --git a/test/fixtures/active_storage/blobs.yml b/test/fixtures/active_storage/blobs.yml index f1f541fc9bf..715c9a5bfe6 100644 --- a/test/fixtures/active_storage/blobs.yml +++ b/test/fixtures/active_storage/blobs.yml @@ -30,3 +30,6 @@ komagata_profile_image_blob: <%= ActiveStorage::Blob.fixture filename: "users/av machida_profile_image_blob: <%= ActiveStorage::Blob.fixture filename: "users/avatars/machida.jpg" %> kimuramitai_profile_image_blob: <%= ActiveStorage::Blob.fixture filename: "users/avatars/default.jpg" %> + +# article +article4_thumbnail_blob: <%= ActiveStorage::Blob.fixture filename: "articles/ogp_images/test.jpg" %> diff --git a/test/fixtures/articles.yml b/test/fixtures/articles.yml index 33a0f734d27..91d4eadfe79 100644 --- a/test/fixtures/articles.yml +++ b/test/fixtures/articles.yml @@ -17,3 +17,9 @@ article3: body: 本文3 user: komagata wip: true + +article4: + title: サムネイル画像付きのブログ記事 + body: サムネイル画像付きのブログ記事 + user: komagata + wip: true diff --git a/test/models/image_resizer_test.rb b/test/models/image_resizer_test.rb new file mode 100644 index 00000000000..6e8f104edfe --- /dev/null +++ b/test/models/image_resizer_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ImageResizerTest < ActiveSupport::TestCase + test '.fit_to!' do + article = articles(:article4) + + ImageResizer.new(article.thumbnail).fit_to!(*ImageResizer::OGP_SIZE) + + article.thumbnail.blob.analyze unless article.thumbnail.blob.analyzed? + assert_equal({ width: 1200, height: 630 }, + article.thumbnail.blob.metadata.slice(:width, :height).symbolize_keys) + end + + test '.just_fit_to_size?' do + article = articles(:article4) + + image_resizer = ImageResizer.new(article.thumbnail) + width, height = ImageResizer::OGP_SIZE + image_resizer.fit_to!(width, height) + + assert image_resizer.just_fit_to_size?(width, height) + assert_not image_resizer.just_fit_to_size?(width + 1, height) + assert_not image_resizer.just_fit_to_size?(width, height + 1) + end +end diff --git a/test/models/image_resizer_transformation_test.rb b/test/models/image_resizer_transformation_test.rb new file mode 100644 index 00000000000..dacecbe313c --- /dev/null +++ b/test/models/image_resizer_transformation_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ImageResizerTransformationTest < ActiveSupport::TestCase + setup do + @image_resizer_transformation = ImageResizerTransformation.new + @width = 1200 + @height = 630 + end + + test '.fit_to' do + assert_equal({ resize: "#{@width}x", gravity: :center, crop: "#{@width}x#{@height}+0+0!" }, + @image_resizer_transformation.fit_to(500, 500, @width, @height)) + end + + test '.resize return 1200x when width magnification is grater than height magnification' do + assert_equal({ resize: "#{@width}x" }, @image_resizer_transformation.resize(500, 500, @width, @height)) + assert_equal({ resize: "#{@width}x" }, @image_resizer_transformation.resize(500, 630, @width, @height)) + assert_equal({ resize: "#{@width}x" }, @image_resizer_transformation.resize(1500, 1500, @width, @height)) + end + + test '.resize return x630 when height magnification is grater than width magnification' do + assert_equal({ resize: "x#{@height}" }, @image_resizer_transformation.resize(1000, 500, @width, @height)) + assert_equal({ resize: "x#{@height}" }, @image_resizer_transformation.resize(1200, 500, @width, @height)) + assert_equal({ resize: "x#{@height}" }, @image_resizer_transformation.resize(1500, 700, @width, @height)) + end + + test '.center_crop' do + assert_equal({ gravity: :center, crop: "#{@width}x#{@height}+0+0!" }, + @image_resizer_transformation.center_crop(@width, @height)) + end +end