VScodeをアップデートする方法

(Mac用)

VScodeを開き、左上のメニューバーより

「Code」→「Check for Updates..(上から2つ目ぐらい)」

で出来ます。

ただし、以下のエラー文により阻まれる場合があります。

Cannot update while running on a read-only volume. 
The application is on a read-only volume. Please move the application and try again. 
If you're on macOS Sierra or later, you'll need to move the application out of the Downloads directory. See https://github.com/Squirrel/Squirrel.Mac/issues/182 for more information.

エラーの内容を一言で言うと「読み取り専用の場所からアプリケーションが起動されているので、アプリケーションの場所を移動してもう一度起動し直せ」ということのようです。

Finderなどのディレクトリ管理ツールを使い、VScode を保存している場所を、別の場所に移しましょう。

自分の場合は、ダウンロードフォルダからアプリケーションフォルダに移せば問題なくアップデートできました。

Vueで画像をリサイズして表示する、そしてそれはバックエンドに送られる(canvasAPI, Base64利用)

あらすじ

fujiten3.hatenablog.com

の続き。

この記事でやること

以前↑の記事にて、簡易的な画像のプレビュー機能 + 送信機能を実装しました。

その内容を一言でいうと、クライアントサイドで画像をBase64形式で符号化し、文字列として取り扱う送信フォームを実装するといったものでした。

が、このプレビュー機能では、受け取った画像をそのままブラウザで表示するため、たびたびブラウザの画面いっぱいを占有し、「デカすぎんだろ…」状態になってしまいます。最近のスマートフォンの高性能カメラで撮った画像のサイズは4032x3024といったものになることがあるので、特にその傾向が顕著です。

これを避ける方法はいくつかあります。たとえば、Vueを使用している場合は、Vuetifyというライブラリが提供するv-avatarのsizeプロパティを利用するなどすれば、特に深く考えずともリサイズが可能です。

しかし、この記事ではcanvasAPIを利用して、つまり「HTMLElementである<canvas>タグをJavaScriptで操作すること」によって、フロント側の画像をリサイズする処理を書いていきたいと思います。

というのも、WebAPIsとJavaScriptを利用すれば、特定のライブラリに依存しない処理を書けるので、様々なプロジェクトで使え、汎用性が高いからです。

つまり、タイトルでは「Vueでリサイズ」となっておりますが、実際には「JavaScriptでリサイズする」、といった内容になります。まあ、Vue上で表現しているからタイトル詐欺ではないでしょう。

また、この記事は前記事を読んでいることがある程度前提条件となっておりますので、ご容赦下さい。

動くもの

挙動をGIFで置いておきます。もともとの画像サイズがどんなに大きくても、一定以下のサイズに、縦横の比率を保ちながら縮尺されます。

f:id:fujiten3:20200324214611g:plain

jsfiddle.net

コードの全体像は上のJSFiddleをご参考下さい。

大雑把な処理の流れ

Base64形式で符号化された画像用の文字列を、HTMLImageエレメントのsrc属性に代入する

そのImageエレメントをもとに、canvasAPIでリサイズした画像を描画する。

リサイズ後の画像を、canvasAPIがもともと持っている関数によって、再びBase64形式の文字列にする。

プレビューのためのImageに再代入

Vue側

new Vue({
  el: "#app",
  name: 'ImageUploder',
  data () {
    return {

      // avatarはimageのsrc属性にHTML側のコートでバインドしている。imageのsrc属性には、Base64形式でエンコードされたimage、または直接URLを指定できる。
      avatar: '',
      message: '',
      error: '',

      // 本筋とは関係ないが、初期画像がないと寂しかったのでプロパティの追加
      initialImageUrl: ''
    }
  },
  created () {

    // 本筋とは関係ない初期画像(サンプル画像)
    this.initialImageUrl = 'https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-600w-1389188336.jpg'
    this.avatar = this.initialImageUrl
  },
  methods: {
    setError (error, text) {
      this.error = (error.response && error.response.data && error.response.data.error) || text
    },

    // この関数の挙動は前記事参照
    getBase64 (file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.onload = () => resolve(reader.result)
        reader.onerror = error => reject(error)
      })
    },
    onImageChange (e) {
      const images = e.target.files || e.dataTransfer.files
      this.getBase64(images[0])
        .then(image => {

          // canvasに渡すためのImageElementを作成
          const originalImg = new Image()

          // src属性に、もともとの画像のBase64形式符号化済み文字列を代入
          originalImg.src = image

          // Imgの画像読み込みが完了したあとに、処理を実行
          originalImg.onload = () => {

            // creatReseizedCanvasElement関数については下記参照
            const resizedCanvas = this.createResizedCanvasElement(originalImg)
            const resizedBase64 = resizedCanvas.toDataURL(images[0].type)
            this.avatar = resizedBase64
          }
        })
        .catch(error => this.setError(error, '画像のアップロードに失敗しました。'))
    },

    // ImageElementを受け取り、それを元に新たな画像をcanvasに描画し直す関数
    createResizedCanvasElement (originalImg) {
      const originalImgWidth = originalImg.width
      const orifinalImgHeight = originalImg.height

      // resizeWidthAndHeight関数については下記参照
      const [resizedWidth, resizedHeight] = this.resizeWidthAndHeight(originalImgWidth, orifinalImgHeight)
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      canvas.width = resizedWidth
      canvas.height = resizedHeight

      // drawImage関数の仕様はcanvasAPIのドキュメントを参照下さい
      ctx.drawImage(originalImg, 0, 0, resizedWidth, resizedHeight)
      return canvas
    },

    // 縦横の比率を変えず、定めた大きさを超えないWidthとHeightの値を割り出す関数
    resizeWidthAndHeight (width, height) {

      // 今回は400x400のサイズにしましたが、ここはプロジェクトによって柔軟に変更してよいと思います
      const MAX_WIDTH = 400
      const MAX_HEIGHT = 400

      // 縦と横の比率を保つ
      if (width > height) {
        if (width > MAX_WIDTH) {
          height *= MAX_WIDTH / width
          width = MAX_WIDTH
        }
      } else {
        if (height > MAX_HEIGHT) {
          width *= MAX_HEIGHT / height
          height = MAX_HEIGHT
        }
      }
      return [width, height]
    },

    // 前の記事参照。APIを叩く処理を記述することを意図して作られた関数。
    upload () {
        if (this.avatar && this.avatar !== this.initialImageUrl) {
        /* postで画像を送る処理をここに書く */
        this.message = 'アップロードしました' 
        this.avatar = ''
        this.error = ''
      } else if (!this.avatar) {
        this.error = '画像がありません'
      } else {
        this.error = '変更前の画像はアップロードできません'
      }
    }
  }
})

以上です。

見づらいですね、ただ複雑な処理はしていないので、一つ一つ処理を追っていけば、難しくはないと思います。

「大雑把な処理の流れ」の項目でも記述しましたが、canvasAPIのdrawImage関数(リサイズのための関数)に対して渡す「ImageElement」を作るための処理を書いている、と思って頂ければ問題ありません。

RailsAPIでBase64エンコードされた画像ファイルを受け取り、S3(AWS)に保存する

自分が昔に書いた記事の続編です。

fujiten3.hatenablog.com

クライアント層から送られてきたBase64エンコードされた画像ファイルをRailsで扱う方法の紹介するのがこの記事の目的です。

以前書いた記事にコメントを頂いたので、筆を取りました。(コメントありがとうございます!)

実装の時間を短縮したいなら……

Rubyの有名なアップロード用Gem「CarrierWave」と、そのさいのBase64エンコード・デコードをサポートする「Carrierwave-base64」を利用するのが手っ取り早いと思われます。

自分も利用したことがありますが、ドキュメントに詳細に使い方が書いているので、迷うところはないでしょう、たぶん。

それらのGemを利用し、AWSのS3に画像をアップロードする処理自体は、↓の記事が参考になりそうでした。(自分が書いたわけではありません。)

https://qiita.com/junara/items/1899f23c091bcee3b058#carrierwave%E3%81%AE%E8%A8%AD%E5%AE%9A

しかし、この記事では別のライブラリを使っています

ただ、この記事ではShrineという添付ファイルを操るための軽量なGemと、自前で実装したBase64デコード・エンコードモジュールを使った実装を紹介しています。

その理由は、この記事は、過去の記事の続編で、そして過去の自分がこのGemを使って実装していたためです!

このGemを使った理由は、CarrierWaveを使ったものはよく見かけたので、別のものに挑戦してみたい、みたいなモチベーションだった気がします。(半年以上前なので記憶が曖昧)

多少オレオレ実装の気があります。ただ、Carrierwave-base64といったGemが行っていることの一部は、この記事を読めば理解の助けになるのではないかと思います。

まずは受け取ろう

Avatarクラスというのを使って自分は受け取る処理を書きました。

class Avatar < ApplicationRecord
  # Shirne用のメソッド
  include ImageUploader::Attachment.new(:image)

  # 自前実装のモジュール
  include ImageEncodable

  belongs_to :user

end
module Api
  module V1
    class AvatarsController < ApplicationController

      # ...

      def create
        # @avatarという、ユーザーの画像を司るクラスのインスタンスを作る
        @avatar = current_user.avatar

        # Base64で送られてきた画像をデコードする(モジュールの説明はのちにします)
        image_file = ImageEncodable.decode_to_imagefile(avatar_params[:image])

        # イメージを格納して、あとはShrineに任せる
        @avatar.image = image_file
        @avatar.save!
        render json: @avatar
      end

     def show
        # ImageEncodableモジュールのおかげで、Avatarクラスのインスタンスが、自分をBase64でエンコードすることが出来る。
        @avatar = @user.avatar.encode(:icon)
        render json: @avatar
     end


      private

        def avatar_params
          params.require(:avatar).permit(:image)
        end
  
end

Shrineが担保するImageUploader(詳細は公式ドキュメントへGo)

require "image_processing/mini_magick"

class ImageUploader < Shrine
  plugin :processing # allows hooking into promoting
  plugin :versions   # enable Shrine to handle a hash of files
  plugin :delete_raw # delete processed files after uploading
  plugin :validation_helpers

  process(:store) do |io, context|
    versions = { original: io } # retain original


    io.download do |original|
      pipeline = ImageProcessing::MiniMagick.source(original)
      versions[:icon]  = pipeline.resize_to_limit!(200, 200)
      versions[:medium] = pipeline.resize_to_limit!(800, 800)
    end

    Attacher.validate do
      validate_max_size 5 * 1024 * 1024, message: "5MBを超える画像はアップロードできません。"
    end

    versions # return the hash of processed files
  end
end

自前実装のモジュール

module ImageEncodable

  # S3に格納している画像データをエンコードし、クライアント層に返すためのメソッド
  def encode(image_size_symbol)
    encoded_image = Base64.strict_encode64(open(avatar_url(image_size_symbol)).read)
    prefix = "data:image/png;base64,"
    prefix + encoded_image
  end


  # Base64の画像をデコードする
  def self.decode_to_imagefile(prefix_encoded_image)

    # Base64の形式を判断し、画像であればそれに合わせた処理
    meta_data = prefix_encoded_image.match(/data:(image|application)\/(.{3,});base64,(.*)/)
    content_type = meta_data[2]
    encoded_image = meta_data[3]
    if content_type == "jpeg" || content_type == "png"

      # Rubyが担保するデコードを行う。StringIOクラスを利用し、ファイルとして扱えるように。
      decoded_image = Base64.strict_decode64(encoded_image)
      image_file = StringIO.new(decoded_image)
    else
      # raise error
    end
  end

  private

    def avatar_url(image_size_symbol)
      if image.try(:[], image_size_symbol)
        if Rails.env.development?
          url = "public" + url 
        else
          url = image[image_size_symbol].url
        end
        url
      else
        "public/default.png"
      end
    end
    
end

以上です。

昔に書いたコードの、不必要そうな処理を除外してベタ貼りしております。

基本的な処理の流れは、クライアント層から送られてきたBase64データをデコードしてファイルにしてS3に保存、クライアントに返すときはエンコードして返答、という流れです。

しかしですね、今思えば、どう考えても、わざわざ画像をS3に保存するときにデコードする必要がないな、と思います。

そのまま文字列で放り込んどいた方が扱いやすいですよね。

まあ、あくまでこんな実装方法もあるよ、という参考にしていただければ幸いです。

2020年1月の振り返り

振り返りの前に

先月は振り返りを書くのが遅れたけど、今月は2日から書けているので優秀!

1月度の感想

(この1ヶ月のパソコン活動:Qbserve)

f:id:fujiten3:20200201221319p:plain

(緑が生産活動、青がニュートラル、赤がTwitterとか。棒1本が1日で左から右に31日間ある)

今月の学習時間: 約36時間

アプリいわく36時間ぐらい。微妙だなー。

徒歩でポッドキャストとか聞いてるの入れても恐らく、勉強時間としての合計は50時間いかないぐらいって感じで、ちょっと少なかったかな、と思います。

ただ、これには言い訳があって、正社員としての労働が忙しかったんですよ、今月。(残業はしてないんだけど)

正社員としての労働で体力を使うと、やはり帰ってから勉強だったりするリソースを割くのが大変。このあたりのバランスと言うか、仕事でパフォーマンス出しながら、帰宅してからの作業の効率を上げるってのは、大事にしていきたいよね。

雑感

進捗駄目です(泣)

MUST(必ずやること)が終わらねーよ、MUSTが重すぎる。過去の俺が未来の俺に負債させてる量が半端ではない。負債がめきめき溜まっていってるんだよな……なんとかしないと。

余談だけど、なんか今月寒かった…。大阪から上京してきた身分なので、東京での初めての冬だったんだけど、まあとくに寒さに違いはなくて、大阪と同じでふつうに寒かったね。起きるたびに「寒いよ〜」って言ってた思う。対策も思いつかなかった。

今月学習したこと

MUST(かならずやる)って先月言ってたやつ

  • Udemy (Understanding TypeScript - 2020 Edition)

終わらし。結構長かったけど、英語の勉強にもなったし、TypeScriptやりたみが増した。しかし実際どこで使おうってのがちょっと課題なんだよな……。

  • 螺旋本

アルゴリズムをやっていっている。JSで書いてます。

しかし、やっぱアルゴリズムの勉強ってあまり即効性感じないと思ってしまうのは僕だけだろうか。(というか、ほんとに役立つのかなー、とちょっと思ってしまってる。)まあatcoderで下から3つ目の色ぐらいにはなりたいと思ってるから、(ゲーム攻略的な意味で)、一応やっていく予定です。数学チックで好きだし。

  • Web API The Good Parts 2章まで

仕事ではほぼRailsAPI触りマンと化しているので読み直し。

10%ぐらい進めた。先月「今月に読み終わるかな?」って言ってた気がする。亀の歩み。

  • 達人に学ぶSQL徹底指南書

また読んでない。2ヶ月連続積みは「一生積みフラグ」だから注意しないとな。

英語

  • Podcast(「Stuff you should kwon」と「Software Engineering Daily」)

  • ゲーム実況

ダンガンロンパシリーズを見終わり。V3おもしろかった!! もっかい見直しながらセリフ覚えていこうかな。

予定してないがやっていたこと

Google拡張機能で遊ぶ

先月に続き、単語帳機能をブラウザ上で実現するために触ってた。引き続きやる予定。

2回勉強会に行った。

blogs.partner.microsoft.com

findy.connpass.com

今月やること

MUST

バックエンドに強くなる系(仕事系)を今月は多めにしようかなあ、と。先に述べたように社内で求められる技術的知識の深さが強くなってきたのと、質問に対して的確な概観を提供しなければならない機会は増えてるから、データベース周りや、APIについて、掘っていく感じの1ヶ月にしたい。

んで、今月は本ベースにする予定。本を消化してるときが、あんたは「えらい!」感が出る。

をやります。なんか先月、MUSTって言ってたのに今月出来てない事が多すぎて「俺はだめだ。目標を達成できていない」感が出てしまってるので、よくない。自分なりに頑張ってるはずなのに目標が達成できてない現実があるせいで、頑張ることに対するインセンティブがめきめき下がっているのを感じる。

なので、簡単な目標を先においておくことにする。これはメンタルテクニックとしては初歩的だが、効果のほどを見てみたい。

WANT(やれればやる)

ここはメモ代わりにおいておく。

  • 達人に学ぶSQL徹底指南書

  • 螺旋本(アルゴリズム

  • パケットキャプチャの教科書

  • OAuth 徹底入門

  • Electron講座

おわりに

今月もわくわくしながら生きていこう! 2月は28日しかない。28日間を有意義に生きるんだ!

あ、1月、リングフィットアドベンチャー買って、やっていってたんですが、首を痛めてしまったので休止しています(ゲームのせいでなく、枕のせいだと思う)。

枕は大事ですね。

以上です。

2019年12月の振り返り

振り返りとは

就業時間以外に学んだことや考えたことをブログとして残しておくこと。

「毎月あげる目標達成の積み重ね → 長期目標の達成」を狙ってます。

振り返りの前に

12月度の振り返りを書くのが遅れたのは、正月にガッツリ体調崩してしまい4日間ほど寝込んでいたからです……。(今日、やっと復活して体調70%モード。)

体調崩したわけは、おそらく、寒い早朝に日記をカタカタ書いて、そこからお酒を飲んで、それで、夜に暴食したからではないかなあ、と思っています。

年末年始のまとまった時間で積んでいた教材なんかを消費したかったので、わりと計画が狂ってショックなところもあるのですが、失った時間は戻らないので、また目標をセットしなおそうという心持ちです。

そして、実家で家族と一緒だからといって、食べるものに気を抜いてはいけないな、というのも思いました。親が作ってくれると、全部食べないと申し訳ないという気になって、食べすぎ傾向にあるのがよくない。ということで、自分のことは自分で管理できるようになるというのが2020年の抱負の1つとなりました。(中学生じみているが)

12月度の感想

いろいろと雑ではあったものの、月初めに今月終わらせたいことを決めたことで、1日1日を無駄にしない意識がかなりついてよかったな、と思ってます。なので、この振り返りの習慣は、続けられる限り続けようかな。

雑感

月初めに立てた目標ベース的には、達成率はそこまでよくない。というのも、やろうと決めていたもの以外に手が伸びることも多かったのが原因かな。ただ、自宅で行う勉強の習慣自体は順調についてきたので、あとは、しっかり質を意識して、時間内で多く学ぼうとすることが大事かなあという感。

学習時間を自動で追跡してくれるデスクトップアプリを導入したので、これを使って、時間管理は無意識的にやっていくつもりです。

今月学習したこと

MUST(かならずやる)って先月言ってたやつ

React + TypeScript

英語教材を主にやっているのは、もちろん、英語の学習も兼ねて。さいきん、ヒアリングより、リーディングに課題感を感じてきているので、わからなかった単語をメモするなりして、語彙力をつけていきたいな。

読むと言っていた本たち

読み終わり。自分が課題感を感じていた、アルゴリズムの初歩的なところについて、広範にインデックスを作ってくれる本でありながら、詳細な解説も随時あってよかったです。コードでアルゴリズム実装してくれてたり、P・NPだったりの世界観を教えてくれたりというのも推せるポイント。

20%ぐらい進めた。今月中に読み終わるかな?

  • 達人に学ぶSQL徹底指南書

読んでねえー。exists関数のとこまで読んでたのは覚えている。

TOEIC

東大駒場キャンパスにて対戦しましたが、最後列近くの席を引き、リスニングで不利を背負ってしまったので再戦希望です。

ドラムを叩いた

久しぶりにスタジオに入ったら下手すぎて泣いた。ストレス解消どころか、ブランクありすぎて「あの頃は俺もうまかったのに……」と哀愁が漂う時間となったしまったので対策が必要。具体的にはしばらくドラムは諦めるか、週1でスタジオ入るか。じゃっかん前者寄り。

予定してないがやっていたこと

Google拡張機能で遊ぶ

単語帳機能をブラウザ上で実現したくて(わからなかった単語を保存しておき、あとで確認する)、すこしChrome拡張機能について触った。今月中に実装終えたほうがよさそう、方針は立ってる。

2回勉強会に行った。

銀座Rails#16 @リンクアンドモチベーション - connpass

Tech Talk vol.2 Backend Engineer 〜マイクロサービスの冪等性〜 - connpass

「白と黒のとびら オートマトン形式言語をめぐる冒険」を読んだ。

コンピューターに計算できることとできないことの境目とはなんだろう、言語とはなんだろう、と、今まで考えもしなかった問いを頭につくってくれて、とても知見のある本でした。

N予備校のプログラミングコースの授業を一部受けた

形式言語論に興味をもったので、その授業もちょっと受けました。

Expressで開発

Docker環境作ってHelloWorldしといた。

「ゼロ・トゥ・ワン」を読んでいる

60%ぐらい。

来月やること

MUST

今月のMUST項目全て。

あとSQLアンチパターン読む。

WHAT

先月書いてたやつと、N予備校のWebエンジニアコース(雑)

あと、「コンピューターシステムの理論と実装」という本を入手したので、こちらもやっていきたい。(相当ヘビーらしいので、計画的に。)

総括と抱負

2019年はエンジニアになるためにずっと勉強していた年で、なんとか10月に東京で就職し、正社員として働かせてもらっていて生き延びているという感じです。

2020年の目標は、ひとまず

  • TOEIC 950
  • 非連続的な成果を求める
  • 勉強の1年

となってます。

英語かな、英語をバンバンやりたいな!

2020年1月は、とりあえず積み系教材をガンガン消化したい。やりたいことがたくさんあることはいいことだけど、やりきった感を覚えるタイミングの消失という問題に出くわしているので、それを防ぐためにもね。

というわけで、みなさま、今年もよろしくおねがいします。