Angularでプレビュー画像を自動回転する
Angularで画像プレビューを行う際に、画像のExif(Orientation)情報を見て自動で向きを回転させる方法です。
JavaScript-Load-Imageという便利なライブラリと、Reactでの使い方を紹介したありがたいブログ記事があったので、かなり簡単に実装することができました。 早速、実装していきたいと思います。
JavaScript-Load-Imageの導入
javascript exif
とかでググるとexif-jsというライブラリが真っ先にヒットするが、JavaScript-Load-Imageの方が断然使いやすいAPIが用意されている印象でした。
まずは、Angularプロジェクトにインストール
$ npm i --save blueimp-load-image
画像プレビュー&回転を行いたいコンポーネントに次のようにしてインポート(モジュール全体をまとめてインポートする必要あり)
import * as loadImage from 'blueimp-load-image';
Exifを見て自動で回転するプレビューを実装
まずは、テンプレート側
<!-- 画像を選択 --> <input type="file" accept="image/*" (change)="onInputChange($event)"/> <!-- 選択した画像を表示 --> <img [src]="image" style="margin-top: 24px">
画像を選択させるinput要素のchangのイベントをイベントハンドラonInputChange
で受けています。
次に、クラス側
... export class RotateComponent { public image: any = ''; onInputChange(event: any) { const file = event.target.files[0]; loadImage.parseMetaData(file, (data) => { const options = { orientation: null, canvas: true }; if (data.exif) { options.orientation = data.exif.get('Orientation'); } this.getDataUrl(file, options) .then(result => { this.image = result; }); }); } getDataUrl(blobImage: Blob, options: Object): Promise<any> { return new Promise((resolve) => { loadImage(blobImage, (canvas) => { resolve(canvas.toDataURL(blobImage.type)); }, options); }); } }
イベントハンドラの引数から取得したfileをloadImageのparseMetaDataメソッドに渡し、Exif情報の取得を行った後、getDataUrl
メソッドでは、loadImageで得られるcanvasをimgタグで表示できるData URL形式に変換しています。
試してみる
試しに、Exif(Orientation)情報が含まれる画像をアップロードしてみます。 ※ 右はJavaScript-Load-Imageを使わすにimgタグにプレビューした画像
参考
ServerlessのTypeScript公式テンプレートを使ってみる
Serverlessをしばらく触ってこなかったので、気づかなかったのですが、v1.21.0からsls create
の際に指定する公式テンプレートにaws-nodejs-typescript
と言うものが追加されたようです。(結構前ですね...)
以前から、プラグインとして、serverless-webpackというものがあり、デプロイコマンド実行時にwebpackを使ってTypeScript→JavaScriptへのビルドタスクを実行するという方法がありましたが、公式テンプレートでもserverless-webpackを使う際の一連のセットアップなどが済んだテンプレートを提供しているようです。
使ってみる
テンプレートからプロジェクトを作成して、とりあえずデプロイしてみます。
プロジェクトの作成
まずは、serverlessのインストールから。
インストールしたバージョンは、現時点での最新版1.24.0
になります。
$ npm i -g serverless
createコマンドでテンプレートからプロジェクトを作成してみます。
$ sls create -t aws-nodejs-typescript -p sls-ts
作成したプロジェクトの内容を確認ると、次のファイルが作成されていました。
./sls-ts/ ├── handler.ts ├── package.json ├── serverless.yml ├── tsconfig.json └── webpack.config.js
aws-nodejs
のテンプレートで作成した際に作成される、ファイルに加え、package.json、tsconfig.json、webpack.config.jsが作成されるようです。
それぞれのファイルの内容を見ていきます。
package.json
プラグインであるserverless-webpack、webpackで使うTypeScriptのローダーなどがインストールされているようです。
{ "name": "aws-nodejs-typescript", "version": "1.0.0", "description": "Serverless webpack example using Typescript", "main": "handler.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { "serverless-webpack": "^3.0.0", "ts-loader": "^2.3.7", "typescript": "^2.5.2", "webpack": "^3.6.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" }
serverless.yml
plugins
にserverless-webpackが設定されています。
service: name: aws-nodejs-typescript # Add the serverless-webpack plugin plugins: - serverless-webpack provider: name: aws runtime: nodejs6.10 functions: hello: handler: handler.hello events: - http: method: get path: hello
webpack.config.js
entry
にslsw.lib.entriesが指定されていますが、これはserverless-webpack側でエントリポイントを自動で解決してくれているらしいです。(./handler.tsを指定したい気分ですが、どんな動きになっているかは後々調べてみよう...)
ts-loaderの設定もされた状態ですね。
const path = require('path'); const slsw = require('serverless-webpack'); module.exports = { entry: slsw.lib.entries, resolve: { extensions: [ '.js', '.jsx', '.json', '.ts', '.tsx' ] }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, target: 'node', module: { loaders: [ { test: /\.ts(x?)$/, loader: 'ts-loader' }, ], }, };
その他のファイルに関しては、割愛します。
とりあえずデプロイしてみる
AWS CLIで必要なcredentが設定されている前提で、デプロイをしてみます。
$ sls deploy
デプロイ中のコンソールを眺めていると、まずwebpack(ts-loader)でTypeScriptが実行され、終わったタイミングでServerlessの実行結果が流れ始めるのがわかります。 ※ グローバルにTypeScriptがインストールされた環境で実行した場合は、そちらが使われるようです。
また、デプロイが完了後に.serverlessディレクトリ以下に生成されている.zipファイルの内容を確認してみると、JavaScriptにビルド済みのhandler.jsが確認できるかと思います。
作成されたAPI Gatewayのエンドポイントを叩いてみると...
動いてますね。
所感
webpackの設定なしに、気軽にTypeScriptが使えるようになってて最高です。
Angularでテキストファイルを読み込む
Angularでブラウザから読み込んだローカルのテキストファイルを表示する方法です。
HTML5のFile APIの基本的な使い方が分かれば簡単な内容ですが、メモ程度に残しておきます。
早速、実装していきたいと思います。
以下で紹介する内容は、angluar-cliでng new
したプロジェクトのapp.componentにべた書きしているので、試しに動かす際はコピペすれば動くはずです。
環境
この記事で紹介する内容は、以下の環境で試しています。
$ ng -v @angular/cli: 1.4.1 node: 8.1.3
実装例
まずは、テンプレート側です。
テキストファイルを読み込むための<input>
と、読み込んだテキストの内容を表示するための<p>
タグを配置しました。
<!-- テキストファイルを選択するinput --> <input type="file" (change)="onChangeInput($event)"> <!-- テキストファイルの内容を表示するエリア --> <p>{{readText}}</p>
次に、クラス側です。
まず、input
のchangeイベントにバインドされたonChangeInput()
メソッド内で、fileオブジェクトをfileToText()
メソッドに渡しています。
fileToText()
メソッドは、FileReaderの結果をPromiseで返します。
※ここではエンコーディング未指定なので、デフォルトのUTF-8 で解釈されます。
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { public readText: string = null; onChangeInput(evt) { const file = evt.target.files[0]; this.fileToText(file) .then(text => { this.readText = text; }) .catch(err => console.log(err)); } fileToText(file): Promise<string> { const reader = new FileReader(); reader.readAsText(file); return new Promise((resolve, reject) => { reader.onload = () => { resolve(reader.result); }; reader.onerror = () => { reject(reader.error); }; }); } }
読み込んだテキストが正しく改行されるように、cssも修正しておきます。
p { white-space: pre-wrap; }
試してみる
試しに、次のようなファイルをアップロードしてみます。
用意したテキストファイル
inputから用意したテキストファイルを選択した結果
以上です。
参考
Angularで画像読み込みに失敗した際の代替えイメージを指定するimgディレクティブを作成
画像読み込みに失敗(404エラーなど)した場合に代替えイメージを表示する方法です。
コンポーネント内で対応する方法
簡単な方法としては、次のような方法があります。
VIew
<img src="imageUrl" (error)="onImageLoadingError">
ViewModel
export class TestComponent { imageUrl = 'https://www.gstatic.com/webp/gallery3/2.png'; defImageUrl = '../assets/images/default.png'; constructor() { } onImageLoadingError() { this.imageUrl = this.defImageUrl; } }
上記の方法では、imgタグの errorイベントを受けてコンポーネント側で保持している、イメージURLを代替えイメージのURLで置き換えるという方法です。
今回は、imgタグに属性ディレクティブを追加する形で代替えイメージの指定を行ってみます。
imgディレクティブを作成して対応する方法
Angularのディレクティブの作成はangular-cli
を使えば、app.moduleへの登録を含め簡単に行うことが出来ます。
次のコマンドでベースとなるディレクティブを作成します。
ng g d directives/defaultImage
コマンド実行後には、app/directives
ディレクトリ以下に次のファイルが作成され、app.moduleへの登録も自動で行われています。
- default-image.directive.ts
- default-image.directive.spec.ts
default-image.directive.ts
import { Directive, Input, HostListener, HostBinding } from '@angular/core'; // Input, HostListener, HostBindingを追加 @Directive({ selector: 'img[default]', }) export class DefaultImageDirective { @Input() default: string; @HostBinding('attr.src') @Input() src; @HostListener('error') updateSrc() { this.src = this.default; } }
あとは、次のように使うだけです。
View
<img src="http://img.tiqav.com/ah.th.gifhttp://img.tiqav.com/ah.th.gif" default="../assets/images/default.png">
Angular MaterialでRouterと連携するTabを実装
Angular Materialで次のようなTabsを使ったUIを実装する方法です。
今回実現したのは👇こんな感じの動きです。
動きを見て分かるように、URLの変化に合わせてアクティブなtabと内容が変更されます。
以下のStackOverflowにあるサンプルコードを参考に、routerとmd-tab-nav-barの連携を行いましたが、これだけでは、URLが直接変更された場合に対応できていなかったため、別途修正を加えました。
環境
$ ng --version @angular/cli: 1.3.0 node: 8.1.3 os: darwin x64
"@angular/material": "^2.0.0-beta.10", "@angular/router": "^4.2.4",
構成
今回作成したプロジェクトの構成は👇
./app ├── ./app/app-routing.module.ts ├── ./app/app.component.css ├── ./app/app.component.html ├── ./app/app.component.ts ├── ./app/app.module.ts └── ./app/container ├── ./app/container/newest │ ├── ./app/container/newest/newest.component.css │ ├── ./app/container/newest/newest.component.html │ └── ./app/container/newest/newest.component.ts ├── ./app/container/random │ ├── ./app/container/random/random.component.css │ ├── ./app/container/random/random.component.html │ └── ./app/container/random/random.component.ts └── ./app/container/search ├── ./app/container/search/search.component.css ├── ./app/container/search/search.component.html └── ./app/container/search/search.component.ts
app.componentに<router-outlet>を配置して、Tabの内容はそれぞれsearch、newest、randomを切り替えます。
サンプル
まずは、routingの設定 (app-routing.module.ts)
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { SearchComponent } from './container/search/search.component'; import { NewestComponent } from './container/newest/newest.component'; import { RandomComponent } from './container/random/random.component'; const routes: Routes = [ { path: '', redirectTo: 'search', pathMatch: 'full' }, { path: 'search', component: SearchComponent }, { path: 'newest', component: NewestComponent }, { path: 'random', component: RandomComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
メインのcomponent (app.component.html, app.component.ts)
<app-header></app-header> <div class="main-area-container"> <nav md-tab-nav-bar> <a md-tab-link *ngFor="let routeLink of routeLinks; let i = index" [routerLink]="routeLink.link" [active]="activeLinkIndex === i" (click)="activeLinkIndex = i"> {{routeLink.label}} </a> </nav> <router-outlet></router-outlet> </div> <app-footer></app-footer>
import { Component } from '@angular/core'; import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; private routeLinks: any[]; private activeLinkIndex = 0; private currentRoute = ''; constructor( private router: Router, private activatedRoute: ActivatedRoute ) { this.routeLinks = [ { label: 'Search', link: 'search' }, { label: 'Newest', link: 'newest' }, { label: 'Random', link: 'random' } ]; router.events.subscribe(event => { if (event instanceof NavigationEnd) { this.currentRoute = event.url.slice(1); console.log(this.currentRoute); this.routeLinks.forEach((elm, index) => { if (elm.link === this.currentRoute) { this.activeLinkIndex = index; } }); } }); } }
ActivatedRouteを使って、コンストラクタ内で現在のパスをsubscribeしています。 routingが変更されるたびに、activeLinkIndexも更新されviewに反映されるようになっています。
その他のrouterで読み込んでいるsearch、newest、randomに関しては省略します。
参考
angular - Angular2 material design mdtabs with router - Stack Overflow
Unicorn pHATを試してみる
pHATシリーズはRaspberry PiのGPIOに接続して、機能拡張ができるというもの。
今回買った、Unicorn pHATは 8x4のRGB LEDが乗ったものです。
スイッチサイエンスでRaspberry Pi Zeroを買うついでにノリで買ってしまったので、セットアップして動かしてみました。
ハードウェアの準備
まずは、Unicorn pHATに付属するピンヘッダーのソケットを取り付けます。 久しぶりにはんだ付けしたので、かなり汚いです…
Raspberry Pi ZeroにはGPIOのピンヘッダが付いていないので、こちらもはんだ付けしました。
使ったのは コレ 。 前に他の用途で買ったものだと思うけど、Unicorn pHATを取り付けると長すぎる気がする。
→強く押し込んだらしっかり刺さった…
ソフトウェアの準備
ターミナルから、必要なソフトウェアの準備を行います。
今回は、Macからssh経由で操作し、ファイルはCyberduckを使いsftp経由で転送しました。
UnicornHatをPythonから操作するためのライブラリをインストールします。
まずは、適当なディレクトリで以下を実行。
$ curl https://get.pimoroni.com/unicornhat | bash
一連のインストールスクリプトの実行が終わると再起動を促されるので、yを選択して再起動します。
Would you like to reboot now? [y/N]
再起動後、再度Raspberry Piのターミナルを開きPythonのインタープリタが使用できることと、インストールしたUnicornHatのライブラリがimportできることを確認します。
一般ユーザーpiで実行したところ、unicornhatのライブラリが/dev/memへのアクセスを要求するため、アクセス権限の問題で実行できませんでした。
今回は、以後rootでPythonを実行することにしました。
$ sudo python Python 2.7.9 (default, Sep 17 2016, 20:26:04) [GCC 4.9.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import unicornhat >>>
とりあえず光らせてみる
>>> unicornhat.set_layout(unicornhat.AUTO) >>> unicornhat.brightness(0.5) >>> unicornhat.set_pixel(0,0,255,0,0) >>> unicornhat.show() >>>
すると、次のように左上のLEDが赤色に点灯します。
unicornhat.set_layout(unicornhat.AUTO)
UnicornHATには通常サイズ(HAT)と半分サイズ(PHAT)があるらしく、それぞれLEDのレイアウトが異なるため、ここで使用するHATの種類を設定します。
ここでは、自動で判別させるためAUTOと指定しました。
unicornhat.brightness(0.5)
1.0を最大値としてLEDの明るさを設定します。
最大の明るさだとかなり明るいので、ここでは半分の0.5としています。
unicornhat.set_pixel(0,0,255,0,0)
点灯するLEDと色を指定します。
第1~2引数でLEDの位置を設定し、第3~5引数は色を設定します。
unicornhat.show()
実際に表示させる命令です。
他も試す
全部点灯
import unicornhat as unicorn from time import sleep unicorn.set_layout(unicorn.AUTO) unicorn.brightness(0.5) width,height=unicorn.get_shape() for x in range(width): for y in range(height): unicorn.set_pixel(x, y, 0, 255, 255) unicorn.show() while True: sleep(1)
get_shape()
でレイアウトの幅と高さを取得できるようです。
その他
Unicorn HATのリポジトリにサンプルがたくさんありました。
まずは、ここらへんで遊んでみると楽しいです(雑)。
雑感
ノリで買ったはいいものの1ヶ月ぐらい放置してしまっていたので、ひとまず動作確認?レベルですが触れてよかった。
「GPIOに接続したLEDを操作する」などに比べると、ハードウェアの組み立て的にも、プログラムの容易さ的にも格段と簡単にLED表示が実現できるデバイスだと感じました。
ライブラリさえインストールしてしまえばPythonから操作できるという点は、WebアプリやSlackBotなどとの組み合わせで可能性が広がりそうです。
Python入門してみる(1)
公式ドキュメントを進めてみた記録。
Python Documentation contents — Python 3.6.1 ドキュメント
初めの章はイントロダクション的な感じだったので、ざっと目を通してスキップ。
2. Python インタプリタを使う
2.1. インタプリタを起動する
起動する
$ python
終了する
Ctrl + D ro quit()
2.1.1. 引数の受け渡し
スクリプト内から引数にアクセスする場合、sys.argvを参照する (例: sys.argv[0])
インタプリタにargument.py というファイルに hogeという文字列の引数を渡して実行する場合は以下のようになる
python argument.py hoge
2.1.2. 対話モード
引数無しでインタプリタを起動した場合は、対話モードで実行される (3つの第なり記号)
以下の状態
$ python Python 3.6.1 (default, Jun 10 2017, 20:33:51) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>>
2.2. インタプリタとその環境
2.2.1. ソースコードの文字コード
ソースコードの先頭に以下を記述すると覚えておく
# -*- coding: utf-8 -*-
と言うのは、Python2系までの話らしい。 Python3ではデフォルトのエンコードがUTF-8となっているのでこの記述は不要とのこと。
3. 形式貼らないPythonの紹介
対話型のインタプリタを実際に触って、サンプルを動かしてみようといった内容。
3.1 Pythonを電卓として使う
3.1.1. 数
$ python Python 3.6.1 (default, Jun 10 2017, 20:33:51) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> 2+2 4 >>> 50-5*6 20 >>> (50-5*6)/4 5.0 >>> 8/5 1.6 >>> 17/3 5.666666666666667 >>> 17//3 # //演算子を使うと小数部を切り捨てて整数部のみを返却する 5 >>> 17%3 2 >>> 5*3+2 17 >>> 5**2 # **でべき乗の計算が可能 25 >>> 2**7 128 >>> width=20 >>> heigth=5*9 >>> width*heigth 900 >>> n Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'n' is not defined >>> 3*3.75/1.5 7.5 >>> 7.0/2 3.5 >>> tax=12.5/100 >>> price=100.50 >>> price*tax 12.5625 >>> price+_ 113.0625 >>> round(_,2) 113.06 >>>
3.1.2. 文字列型(string)
文字列操作に関して。
$ python Python 3.6.1 (default, Jun 10 2017, 20:33:51) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> 'hoge moge' 'hoge moge' >>> 'doesn\'t' "doesn't" >>> "doesn't" "doesn't" >>> '"Yes," he said.' '"Yes," he said.' >>> "\"Yes,\" he said." '"Yes," he said.' >>> '"Isn\'t," she said.' '"Isn\'t," she said.' >>>
ここまでチュートリアルに記載の内容を順に進めてきたが、幾つかのサンプルをスキップ。
雑感
高専時代はスーパー不真面目学生だったこともあり、授業でプログラミング言語を勉強した記憶は殆ど無いように感じる。
作りたいものベースで、必要な言語やフレームワークの使い方を都度ググりながら覚えるというようにプログラミング言語と向き合ってきたので、
公式のチュートリアルを進めるという経験は、案外初めてかもしれない。
あとで見返してみると、中身の無い記事だろうと感じるだろうけどひとまずPythonに関してはこんな感じで続けようと思っている。