2016/02/21

[js]サイズに合わせた画像の縮小

このエントリーをはてなブックマークに追加

画像を任意のサイズに縮小するときの計算方法について説明します。
ソースコードにはJavascriptを使っていますが、なるべく言語に依存しないようにするつもりです。

この手の計算は、寄り付かない人には、ものすごく苦手意識があるのではないかと想像します。
落ち着けば、大変な計算はぜんぜん出てきません。
また、理屈を簡潔に語るよりも、計算から感覚的にイメージがつかめるほうがよいと思ったので、わりとくどくど説明しています。


画像を任意のサイズに合わせる


画像を任意の矩形サイズに合わせたいとします。
やってくる画像のサイズはまちまちですが、なるべく画像の全容が見えるようにして、収まらない部分は切り落としてしまうことにします。


当然ですが、幅と高さにサイズを指定して、強引にギュッとゆがめてしまうのは画像にもよろしくありません(変倍というやつです)。
幅と高さは同じ比率(等倍)で収めるものとします。


横長画像か縦長画像かを判定する


画像によって、処理の仕方は2パターンあります。
横長の画像は左右が余るようにしたいですし、縦長の画像は上下を余るようにしたいですね。

具体的な数をもとに考えることができるように、例として、フラミンゴの画像とヒマワリの画像を使います。
それぞれの画像サイズはこちら。
高さ
Aフラミンゴの画像450px300px
Bヒマワリの画像400px400px
描画先の矩形サイズ100px80px

フラミンゴの画像とヒマワリの画像を、描画先の矩形サイズに合わせて縮小したいとします。

それぞれの縦横の比率を比較することで、どちらに合わせる画像かを判別させます。

フラミンゴの縦横比率(幅/高さ)= 450px / 300px = 3/2 = 1.5
ヒマワリの縦横比率(幅/高さ)= 400px / 400px = 1.0
描画先の矩形の縦横比率(幅/高さ)= 100px / 80px = 5/4 = 1.2

縦横比率の関係は、
フラミンゴ(1.5)  > 描画先の矩形(1.2) > ヒマワリ(1.0)
となっています。

描画先の矩形よりも縦横比率が大きいものは横長として扱えばよく、小さいものは縦長として扱えばよいことが解りますね。


切り出す画像と縮小率を計算する


必要となるのは、画像のどの部分を切り出して、どのくらい縮小すればいいかという2つです。
ひとまず横長画像について考えます。


先に縮小比を求める
どのくらい小さく(または大きく)すればいいのかを考えます。

横長の画像は左右に余りますので、画像の高さを、描画先の矩形の高さに合わせると考えると解りやすいですね。

縮小比 = 描画先の矩形の高さ / 画像の高さ
です。

※縮小と書いていますが、計算式には縮小と拡大の区別はありません。画像が小さくて、描画先の矩形のほうが大きいときは、縮小比が1よりも大きくなるだけですね。

切り取り領域を決める
画像から領域を切り取り、縮小することで、描画先の矩形に合わせます。
どの部分の領域を切り取ればいいかを求めます。

・高さ
高さはそのまま、画像の高さを描画先の矩形の高さにするだけです。

切り取る領域の高さ = 画像の高さ

・幅
幅について考えます。
切り取る領域の幅に縮小比を掛ければ描画先の矩形の幅になるはずなので、切り出し幅を求めるためには逆に縮小比で割ってあげればよいでしょう。

描画先の矩形の幅 = 切り取る領域の幅×縮小比
なので、
切り取る領域の幅 = 描画先の矩形の幅/縮小比
で計算できます。

・切り取りの開始位置
また切り取りの開始位置は横幅の画像は左右に余る分を、少しずらせばいいことが判ります。
画像の幅と切り取り幅の差分があまりの部分ですので、真ん中の部分を切り取るためには、差分を等分すればよいですね。


ということで、
切り取りの開始位置 = (画像の幅 - 切り取り幅) / 2
で計算できます。


縦長の画像のときの計算

縦長の画像も同じように計算しましょう。
縦長の画像の場合は、縮小比は幅の比率になります。

縮小比 = 描画先の矩形の幅 / 画像の幅

・幅
切り取る領域の幅 = 画像の幅

・高さ
切り取る領域の高さ = 描画先の矩形の高さ/縮小比

・開始位置
開始位置は、上下の位置を決めます。
縦の切り取りの開始位置 = (画像の幅 - 切り取る高さ) / 2
で計算できます。


ソースコード

サンプルとして、画像をロードし、幅250px、高さ100pxに合わせて縮小するソースコードを載せておきます。
なお、言語はJavascriptです。
html内にcanvasのidを持つcanvasタグが配置されているものとします。

var imageFile = "/Images/ヒマワリ.jpg";
var id = "canvas"
var canvasWidth = 250;
var canvasHeight = 100;

var canvas = document.getElementById(id);
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var context = canvas.getContext('2d');

var image = document.createElement('img');
image.src = imageFile;

// 準備
image.onload = function () {
    var imageWidth = image.width;
    var imageHeight = image.height;

    // ①縮小比を計算
    var scale = 1.0;
    if (imageWidth / imageHeight > canvasWidth / canvasHeight) {
        scale = canvasHeight / imageHeight;
    } else {
        scale = canvasWidth / imageWidth;
    }

    // ②切り出し領域を計算
    var clipWidth = canvasWidth / scale;
    var clipHeight = canvasHeight / scale;
    var clipX = (imageWidth - clipWidth) / 2;
    var clipY = (imageHeight - clipHeight) / 2;
  
    // ③drawImageメソッドの呼び出し
    context.drawImage(image, clipX, clipY, clipWidth, clipHeight, 0, 0, canvasWidth, canvasHeight);

};


解説

計算に使っている変数をざっとリストアップしておきます。
imageWidth画像のオリジナルサイズの幅
imageHeight画像のオリジナルサイズの高さ
canvasWidth指定矩形の幅
canvasHeight指定矩形の高さ
scale縮小比
clipX切り取り領域の開始X座標
clipY切り取り領域の開始Y座標
clipWidth切り取り領域の幅
clipHeight切り取り領域の高さ

準備として、画像を読み込んでから処理されるようにしたいのでonloadイベントに記述しています。


①縮小比を計算

縦横比率を比較し、横長の画像と、縦長の画像に分けて縮小比率を計算しています。


完全に横道にそれますが、割り算は掛け算に比べてものすごく遅いです。
(imageWidth / imageHeight > canvasWidth / canvasHeight)
は、下のようにも書けます。
(imageWidth * canvasHeight > imageHeight * canvasWidth)
こちらのほうが高速です。


②切り出し領域を計算

画像の切り出し領域を計算しています。
横長でも縦長でも縮小比が解れば同じ計算式で導くことができます。


ただし不要な演算が増えているので注意。


③drawImageメソッドの呼び出し
canvasのdrawImageメソッドを呼び出しています。
drawImageは、canvasにイメージを描画するためのメソッドです。
drawImageは引数の多い9つあるのを使っています。

context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)


sx,sy,sw,shで描画元からコピーする矩形を指定し、dx,dy,dw,dhで描画先の矩形を指定します。


以上で終わりですが、もう一つだけ足しておきます。


おまけ


上記は、できるだけ画像を矩形いっぱいに広げて収めるという要件でした。
そのため矩形よりもはみ出た領域は切り取られてしまいます。

替わりに、画像を切り取ることなく、画像の全体を矩形に表示したいとします。
こういうのもありそうな話です。

こういう感じですね。

横長画像か縦長画像かを判定するために縦横比率を比較しました。
この判定を反転させます。

上のソースコードでいうと、
if (imageWidth / imageHeight > canvasWidth / canvasHeight) {

if (imageWidth / imageHeight < canvasWidth / canvasHeight) {
にする。
するとあら不思議。

幅と高さのどちらを基準にサイズを合わせるか、というのを逆転させればよいので、これだけでできるのです。


おしまい。

2016/02/09

[C#]2つの集合の差分を調べる

このエントリーをはてなブックマークに追加
集合が2つあって、片方にはなくて、もう片方には存在する要素の差分を調べます。
例として、常用漢字にある漢字で、JIS第1水準にはない漢字を調べるコードを書きます。

常用漢字とJIS第1水準とはなにかをざっくり


常用漢字
https://ja.wikipedia.org/wiki/%E5%B8%B8%E7%94%A8%E6%BC%A2%E5%AD%97

常用漢字とは「現代の国語を書き表す場合の漢字使用の目安」として、国が選定した漢字です。2136字あります。

JIS漢字コード
https://ja.wikipedia.org/wiki/JIS%E6%BC%A2%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89

JIS漢字コードの第1水準漢字とは、日本工業規格において定められた漢字コードのうち使用頻度が高いとされている漢字です。2965字です。

常用漢字はよく使われる目安として出版物や教育に関わるようです。方や、JIS漢字コードはコンピュータ上の日本語表示の文字コードとして作られていたもの。第4水準という一度も見たことのない漢字まで一通り決められています。ざっくり理解ですが、作られた目的は全然違うようです。
しかし、どちらもよく使われる漢字同士なので似通った集合なわけですね。

常用漢字は2136字で、JIS漢字コードの第1水準漢字は2965字です。もちろん、最初から人力で調べる気はありませんので、プログラムに頼りましょう。

コード


常用漢字とJIS漢字コードの第1水準漢字のリストは、こちらのサイトを参考にさせていただきました。

漢字辞典オンライン
http://kanji.jitenon.jp/

漢字のリストは、2つともテキストファイルにしてます。
ここに置いておきます。

// using System.IO;

private void button1_Click(object sender, EventArgs e) {
        
    string joyo = this.ReadFile("常用漢字(2136字).txt");
    string jis = this.ReadFile("JIS第1水準(2965字).txt");

        // 差分を拾う
    string sabun = new string(joyo.Where(n => !jis.Contains(n)).ToArray());

    Console.WriteLine(sabun);
}

private string ReadFile(string file) {
    StreamReader reader = new StreamReader(file);
    string text = reader.ReadToEnd();
    reader.Close();

    // 改行を省いておきます
    text = text.Replace(System.Environment.NewLine,"");

    return text;
}

実行結果

上のコードを実行した結果です。

丼刹拉訃恣哺惧羞貪喩彙楷毀嗅傲慄箋瘍辣憬摯踪嘲諧錮緻曖璧籠鬱

はい。
30文字ヒットしました。

解説

解説と言うほどのことはしていませんが、コードはサンプルとなる文字列を拾ってくる部分がほとんどです。
「常用漢字(2136字).txt」、「JIS第1水準(2965字).txt」のファイルは実行ファイルの直下に必要です。
ファイルの存在確認すらしてないコードなのでご注意ください。

なんだかんだで、主要な処理は下の一行です。
string sabun = new string(joyo.Where(n => !jis.Contains(n)).ToArray());

joyoにある文字を走査して、jisに存在していない漢字を文字列として再構築しています。
  • stringはcharの配列として利用できますので、そのままLinqのWhereメソッドを利用します。
  • jis.Contains(n)は、joyo内の1文字(n)がjis内に含まれていたらtrueを返します。
  • joyo.Whereメソッドは、否定演算子(!)があるので含まれなかったものだけを戻していきます。
  • 最後に、charの配列から文字列stringを生成してます。


途中にある否定演算子(!)をなくせば、「どちらにも存在する要素を調べる」コードに早変わりします。
string sabun = new string(joyo.Where(n => jis.Contains(n)).ToArray());

こちらのコードでは、常用漢字とJIS第1水準のどちらにも該当する漢字が抽出できます。

おまけに


サンプルとして使用した常用漢字と第1水準漢字のことばかり喋っていますが…。
(知らなかった分、気づきが多かったです)

JIS第1水準は3000字近くあるので、常用漢字を包含しているかと思いきや、そういうわけでもないんですね。
というのも、常用漢字は最近(2010年)に改定されていて、差分文字の多くはそのときに追加されたもののようです。
  • 「丼」とか。たしかに近年になって頻度が増えたのでしょう。
  • 「嵐」とか「韓」とか。流行りとかではないのかもしれないけれど、文化ってそういうものですものね。
  • 「鬱(うつ)」が常用に、というのはいい気はしません。

もちろんJISの漢字コードを改定するわけにいかないですからね。


はい、以上です。
お粗末さまでした。

2016/02/02

Windows.oldフォルダは消していいのか?

このエントリーをはてなブックマークに追加
知らないうちに増えているフォルダをみると、どうしていいものかわからなくなります。
まして、結構サイズが大きかったりする。
なんとなく、特殊な名称をしているので、わからなくはないですけれど。


結論から。
放置で勝手に消えます

Windows.oldフォルダとは?

Windows.oldフォルダは、Windowsアップデートのときにつくられます。
何かあったときに戻せるように古いシステムファイルが保管されたフォルダということです。
このフォルダは、アップデート後28日が経過すると勝手に削除されるらしいので、基本的には放置でよさそうです。

勝手に消えるということがどこかに書いてあるわけではないので、見ず知らずのフォルダが増えていたらギョッとしますね。
Windowsフォルダはシステムドライブの直下にあって、Windowsのシステムまわりを少し触ったことがある人には馴染みがあるでしょう。
システムドライブのすぐ下のフォルダには下手に触らないほうがいいと思うのは普通のことなのですが、サイズを調べてみるとやたらと大きいので、知らないとこれはどうしたものかと思ってしまいます。

ドライブの残りの容量に不安があるなど、少しでも余計なファイルを残しておきたくないという人は、Windowsに入っている「ディスククリーンアップ」というツールを使うことで手動で削除することもできます。


Windows.oldフォルダを削除する


「ディスククリーンアップ」を使うことで、Windows.oldフォルダだけでなく、アップデート時にシステムが据え置きしているファイルも消すことができます。これが結構大きいです。

システムにかかわるファイルやフォルダの移動や削除などを直接操作するというのは、あんまりよろしくありません。どこでどういう操作がどこに関わってくるのかを理解してやらないといけないからですね。Windowsが用意している「ディスク クリーンアップ」というお掃除ツールがあるので、できるだけお任せするほうがいいでしょう。

ディスク クリーンアップ
スタートメニューから、「Windows管理ツール」のなかの「ディスク クリーンアップ」を選び、起動させます。
あるいは「コントロールパネル」→「システムとセキュリティ」→「管理ツール」から探すこともできます。

しばらくして画面がでます。「システム ファイルのクリーン アップ」を押します。

ドライブを聞いてくるので、システムドライブ(通常はCドライブ)を選択します。
「どのくらいの空き容量を作成できるのか計算しています。」という画面が出ます。しばらく待ちましょう。

削除するファイルに並んでいるリストが増えました。
リストのなかから、とくに大きいのは、「以前のWindowsのインストール」と「一時Windowsインストールファイル」です。足して7GBほどありました。
「以前のWindowsのインストール」にチェックする操作が、Windows.oldフォルダーを削除する操作になります。
「一時Windowsインストールファイル」は、セットアップに使用された一時ファイルなので、削除しても問題ありません。

必要な項目にチェックをつけて「OK」を押します。


「このコンピューターにある不要なファイルを整理しています。」という画面になります。
途中で、「以前のWindowsのインストールまたは一時インストールファイルをクリーンアップすると、コンピューターを以前のバージョンのWindowsに復元することはできなくなります。この操作を実行しますか?」というメッセージがでます。「はい」を押します。

以上です。
Windows.oldフォルダがなくなっているのを確認しにいきましょう。

Windows.oldフォルダという馴染みのないフォルダの出現を不審に思ってしまうのは、おかしなことではないと思います。アップデートのたびによくわからないディスク容量の増減があって、そこに気づくことができるのは、それだけ日ごろから気を配っているからですね。
ではでは。

Windows.old フォルダーを削除する方法 - Windows ヘルプ
http://windows.microsoft.com/ja-jp/windows-8/how-remove-windows-old-folder