トリアエズぶろぐ
フリーランスでやってる77世代のWEBプログラマが福岡からのんびりとお送りいたします。
スポンサーサイト


一定期間更新がないため広告を表示しています

JS+CanvasでInstagram風エフェクト #Canvasの基本からコントラスト調整まで


普段JavaScriptやCanvasを扱っていると、

JavaScript+CanvasでInstagram風の写真エフェクトが作りたい!
ラーメン画像をもっと美味しく見えるように加工したい!

って誰もが3日に2度くらいは思いますよね。(?)


ということで、Canvasの画像加工についてつらつらと書いていきたいと思います。

最近ではCSS3でのフィルタ機能なども充実していますが、画像として書き出したり
スタンプを付けたり、スマートフォン上で動かしたりしたいときに役に立つかもしれません。

今回は基本的なCanvasに画像を読み込み、簡単な加工、コントラスト調整までやってみましょう。
#一部jQuery使ってます
 
それでは早速、Canvasにラーメン画像を読み込みましょう。

 
[HTML]
<canvas id="myCanvas"></canvas>
 
[JavaScript]
// canvasオブジェクトをゲットします
var canvas = $("canvas#myCanvas")[0];
// コンテキストをゲットします。
var ctx = canvas.getContext('2d');
// Imageオブジェクトを作ってラーメンを読み込みます
var img = new Image();
img.onload = function(){
// 読み込み後、キャンバスのサイズをラーメンに合わせます。これ重要。
    $("canvas#myCanvas")
        .attr({
            width: img.width,
            height: img.height
        });
    // ラーメンをキャンバスに描画します
    ctx.drawImage(img, 0, 0);
}
img.src = "ramen.jpg";

読み込みはこれだけ。簡単ですね!

結果はこちら




さて、ここからが写真エフェクトの第一歩です。
読み込んだ画像のピクセルにアクセスしてみましょう。

先程
ctx.drawImage(img, 0, 0);
でコンテキストにラーメンを描画しましたが、コンテキストからイメージの生データ(ImageDataオブジェクト)を取り出すことができます。

var imageData = ctx.getImageData(x, y, x2, y2);
x, y, x2, y2 の矩形の範囲で取得できます。通常は 0, 0, 画像の幅, 画像の高さ を使うことになるでしょう。

データへのアクセスは
imageData.data
です。
imageData.data の中身は、RGBAの順番で並んだ1次元配列です。

(1ピクセル目)RGBA(2ピクセル目)RGBA・・・

と言った感じです。

一番左上 (0, 0) のピクセルは

red = imageData.data[0];
green = imageData.data[1];
blue = imageData.data[2];


ということになります。
(100, 100)の場所にあるピクセルは
x = 99;
y = 99;

データのスタート位置が
pos = (y*画像の幅 + x) * 4;
となり、

red = imageData.data[ pos ];
green = imageData.data[ pos + 1];
blue = imageData.data[ pos + 2];


ということになりますね。
この red, green, blue の値を変更して写真エフェクトを作る流れとなります。

変更したデータをコンテキストに書き戻すには、
ctx.putImageData(imageData, 0, 0);
です。

ちょっとわかりにくいかもしれませんが、コードを見ると分かりやすいと思います。
ということで、早速ラーメンを加工してみましょう。
   
[JavaScript]
    // ImageDataオブジェクトをゲット
    var imageData = ctx.getImageData(0, 0, img.width, img.height);
    
    // ぐるぐる回します。
    for (var y=0; y<img.height; y++){
        for(var x=0; x<img.width; x++){
            // データの開始位置
            pos = (y*img.width + x) * 4;
            
            // 平均色を作る
            gray = ~~((imageData.data[pos] + imageData.data[pos+1] + imageData.data[pos+2])/3);
            
            // 平均色を配列に書き込み
            imageData.data[pos  ] = gray;
            imageData.data[pos+1] = gray;
            imageData.data[pos+2] = gray;
        }
    }
    // 書き戻す
    ctx.putImageData(imageData, 0, 0);

画像の幅と高さを使ってぐるぐる回すのがコツですね。

結果はこちら



いかがでしょうか。意外と単純な仕組みですよね。

でも…ラーメンをモノクロにしてもおいしそうに見えない…

ということで、今回は一歩踏み込んで写真のコントラストを上げてみましょう。

 
コントラストとは、画像の明るい部分と暗い部分の差を言います。

コントラストが強い ⇒ 明暗の差が強い
コントラストが弱い ⇒ 明暗の差が弱い

ということです。
視覚で分かりやすいように簡単なグラフを作ってみました。
スライダーを動かしてみてください。




コントラストを上げると明るいところはより明るく、暗いところはより暗くなります。
逆にコントラストを下げると、明るいところは暗く、暗いところは明るくなります。

このように、画像の中にある様々なピクセルの明るさ(0〜255)に対して明暗の調整をすることで
コントラストの高い画像、低い画像を作ることができます。

コントラストのアルゴリズムはズバリこれ。
#valueは画像の明るさ、contrastには -127〜127 の値を与えます。
[JavaScript]
function _contrast(value, contrast){
    
    value = ~~value;
    contrast = ~~contrast;
    
    var aa;
    if (contrast > 0) {
        aa=255/(255-(contrast*2));
    } else {
        aa=(255+(contrast*2))/255;
    }
    return ~~(aa*(value-127))+127;
    
}

これを使って、ラーメンをより美味しくしてみましょう。

コントラストを32、ご飯系なのでより美味しくするよう REDを+8 してみます。
#ImageDataは0〜255の整数でなくてはならないため、modifyColorという関数で値を丸めてます。
[JavaScript]
        var imageData = ctx.getImageData(0, 0, img.width, img.height);
        for (var y=0; y<img.height; y++){
            for(var x=0; x<img.width; x++){
                
                pos = (y*img.width + x) * 4;
                
                r = imageData.data[pos];
                g = imageData.data[pos+1];
                b = imageData.data[pos+2];
                
                r = _contrast(r, 32) + 8;
                g = _contrast(g, 32);
                b = _contrast(b, 32);
                
                r = modifyColor(r);
                g = modifyColor(g);
                b = modifyColor(b);
                
                imageData.data[pos  ] = r;
                imageData.data[pos+1] = g;
                imageData.data[pos+2] = b;
            }
        }
        
        ctx.putImageData(imageData, 0, 0);
// rgb値を0から255に収める
function modifyColor(color){
    color = ~~color;
    return color>255 ? 255 : (color<0 ? 0 : color);
};
 

結果はこちら




元画像

と比べたらずいぶんメリハリが付いて美味しく見えるようになりましたね!


いかがでしょうか。
これができればあとはこっちのもの。
RGBの値を変更していくことで写真に様々なエフェクトを適用することができます。

 
JS+Canvasで様々な写真エフェクトや、SVGでスタンプを合成できるサイトを公開しています。
PC/スマートフォン対応、画像の保存までできます。ぜひお試し下さい。
#JavaScriptなので、僕のきちゃないソースコードも丸見えです。
 

Photo Effects Online powered by tuna.be


今後、このサイトで実装している

・もっとInstagram風なフィルタの作成、スクリーンやオーバーレイ合成のアルゴリズム
・画像の回転
・レンズケラレのシミュレーション
・レンズ端の画像の流れのシミュレーション
・JSで時間のかかる処理を行うときにスクリプトが止まらないようにする
・SVG画像の描画、色やサイズの変更

あたりをブログにできればと思ってます。

それでは、よいCanvasライフを!Enjoy!
 
[jQuery] touchPhotoViewerをバージョンアップしました


以前、FacebookやTumblr等のダブルタップで画像をズームとかするアレ、をjQueryで書きました。

facebookやtumblrのスマホアプリでよく見る写真をフルスクリーンで見てダブルタップで拡大とかする感じのjQueryを使ったJavaScriptプログラムを試しに書いてみた  

改めて使っているとちょっとダサいなーというところがあったので久しぶりにバージョンアップしましたよ。
 
・サムネイルをタップするとブラックアウトしてフルスクリーンで画像を表示
・ダブルタップすると原寸大切り替え
・スワイプでスクロール
・(シングル)タップで消えます。

に加えて、

・ズームした時の慣性スクロール
・ダブルタップした箇所を中心時ズーム

に対応しました。
プラグインにはしてません、ごめんなさい。

DEMOはこちら(スマホで見てね)


使い方は以前と同じ。
// headタグ内で読み込み
<script type="text/javascript" src="./touchPhotoViewer.js"></script>
// imageのリンクで rel="touchPhotoViewer" を追加
<a href="photo_large.jpg" rel="touchPhotoViewer"><img src="photo.jpg" /></a>

えんじょい!
facebookやtumblrのスマホアプリでよく見る写真をフルスクリーンで見てダブルタップで拡大とかする感じのjQueryを使ったJavaScriptプログラムを試しに書いてみた


タイトルそのまんまです。
jQueryプラグインとか探してみたのですが、丁度いいのがなかったので書いてみました。
jQueryプラグインではなく、普通のjQueryを使ったJavaScriptです。

・サムネイルをタップするとブラックアウトしてフルスクリーンで画像を表示
・ダブルタップすると原寸大切り替え
・スワイプでスクロール
・(シングル)タップで消えます。

DEMOはこちら(スマホで見てね)

使い方は簡単!
// headタグ内で読み込み
<script type="text/javascript" src="./touchPhotoViewer.js"></script>
// imageのリンクで rel="touchPhotoViewer" を追加
<a rel="touchPhotoViewer"><img src="photo.jpg" /></a>
ちゃちゃっと書いたのでアレですが、お試し頂けると幸い。
早速つなビィに組み込みました。

そんじゃーね!
iPhone/スマートフォンアプリのバージョンアップ


つなビィのiPhone/スマートフォンアプリは右上にメニューボタンを設置しています。
今までは日本人は右利き多い、メニュー開いて右手のまま記事を書きたい、という理由でメニューボタンを右上に設置してたんです。

でもiPhoneの縦長化やスマホの巨大化に伴って右上のメニューボタンも届きづらくなる事態に…押しづらい…。
更にiOS7のフラットデザイン化でダサさが顕著になったりステータスバー(時計の部分)にアプリ画面が被ってたり…うむむ

このままでは不便・ダサい極まりないのでアプリのバージョンアップに手を出してます。

流行りに乗ってBootstrap3ベースのフラットデザインで構築、下部にメニューバーを持ってきました。
アイコンもBootstrap3のウェブフォントです。簡単便利!



そしてメニューですがこれまで右側からスライドするようにメニューを表示してましたがそれも撤廃、iOS7の半透明レイヤーを意識したメニューにしました。
PhoneGap(HTML+CSS+JS)で構築してるのでこのレイヤーメニューもJavaScriptで力技で表現してます。(jQueryプラグインとして公開してます)



メニューもフラットな感じで。ちょっとシンプルすぎるので何か足したいところ。

Webの方の管理画面なども合わせるようにやってるのでなかなかに大変。
でもアプリ(ウェブアプリも)ってやってて楽しいですねー。

さて、ストア公開までもう少し粘ってみます。
iOS7っぽい背景がボケたメニューを開くjQueryプラグイン


 を、勢いで作ってみますた。
ネイティブに比べればなんちゃってですが、使ってみて下さいな。

使い方は簡単なので、ソースを参照してください〜

サンプルはこちら↓

2013.09.17 gitHub公開しましたー




これでウェブでもiOS7気分が味わえますね!(?)
えんじょいっ


【jQueryプラグイン】カラーピッカー【ExColor】をスマートフォンに対応しました。



ExColorは軽量のカラーピッカーjQueryプラグイン。
ググると色々と記事が出てくるのですが、派生元がよくわかりません…。

確かに軽くて使いやすいのですが、スマホ対応はしていない模様。
クリックで色の選択はできますがカラーボックスの中をスワイプできない。

てなわけで
さんよりforkさせて頂きました。
タッチイベントに対応しただけですが。

興味のある方はご利用下さい〜。
↓ダウンロードはこちら
OperaのCanvasにバグ?


とある条件でOperaのCanvasにdrawImageできないバグがあるっぽいので備忘録。

小さめの画像(例えば100x100)が【canvas】に描かれているとする。
新しいCanvasに拡大して描画したい時

var newCtx = newCanvas.getContext('2d');
newCtx.width = 1000;
newCtx.height = 1000;
newCanvas.setAttribute('width', 1000);
newCanvas.setAttribute('height', 1000);

// ピッタリに拡大しようとすると描画されない
newCtx.drawImage(canvas, 0, 0, 1000, 1000);

// 1px小さいと描画される
newCtx.drawImage(canvas, 0, 0, 999, 999);


回避策を知ってる方は教えて下しあ…><

[PhoneGap] ページを表示した時に自動でキーボードを表示するのは不可能?



PhoneGapアプリで画面を開いた時にフォームにフォーカスを当ててソフトウェアキーボードを自動的に表示したーーい!


というわけで。


通常のWEBでページを開いた時にフォーム部品にフォーカスを当てるには

<input type="text" autofocus="autofocus" /> (html5)


$("input").focus();

でOKですが、PhoneGapアプリで画面を開いた時に
フォーム部品にフォーカス ⇒ キーボード自動表示
するのは残念ながら不可能のようです。(※PhoneGapプラグインを利用すれば別)

試したのはこんな感じ(iOS6で検証)

<input type="text" autofocus="autofocus" />
$("input").focus()
$("input").select()
$("input").click()
$("input").trigger("click")
$("input").trigger("touchstart").trigger("touchend").focus()
$('input').focus().trigger( jQuery.Event("keydown", { keyCode: 37 }) );
setTimeout(function(){ $("input").focus(); }, 1000);

組み合わせも色々試しましたが、全滅でした(´・ω・`) 
実際にユーザが画面をタップしないとキーボードは出てこないみたいですね。
もし成功された方はそっと教えて頂けると嬉しいです><


以下のページではPhoneGapプラグインを作成することで実現しようとしてるみたいです。自分は試してません…。

ご参考までに〜

[iOS & Android] ブラウザでフリックメニューとPull to RefreshできるjQueryプラグインの習作


※2012.10.06追記
Android(2.2+で確認)ブラウザでも動作するver0.0.2をアップしましたー

※2012.11.12追記
ver0.0.3をアップしました。詳細は後述でー。

今までほとんどjQueryPluginを書いたことがなかったのですが、スマホのWEBでもPathやFacebookのようなフリックしてメニューがシュパッと表示されるインターフェイスや画面を下に引っ張ってリロードするPull to Refreshが作りたいなーと思い、練習がてら書いてみました。

有名なプラグインとしては既にpageslideiScrollなどがありますが、ヘッダを固定すると期待通りの動作にならなかったり、人の書いたコードなのでカスタマイズも難しかったりしたので車輪の再発明&練習ということで。

ダウンロード


現在3種類のバージョンをアップしてます。

ver0.0.1 … overflow-y:scroll & -webkit-overflow-scrolling:touch を使ったもの。
Androidブラウザはサイドメニューは開けるものの、フリックで開いたりPull to Refreshに未対応

ver0.0.2 … コンテンツ部を独自スクロールにしたもの。
Androidブラウザでもフリックメニュー&Pull to Refreshに対応しました。

ver0.0.3 … 色々と試行錯誤してきましたが、ひとまず現在のところの最終版的な。
できるだけスムーズに動くようにコンテンツ部をtransform/transitionでスライドするようにしました。
しかしCSS3アニメーションの弊害としてposition:fixedが効かなくなるので、ヘッダ部の動きは独自に実装しています。



ヘタクソなプラグインですが、使ってみたい方はご自由にどぞー。ライセンスはGPLとしています。
スライドメニューのBG画像やヘッダのボタン画像も作ったものですのでこれもご自由に。



実装する方法は色々なパターンがあるけどどうしよう?


さてさて、iOSのSafariなどでヘッダを固定するようなコンテンツを作成しようとした場合、ヘッダ部をposition:fixedする方法と、コンテンツ部をoverflow-y:scroll & -webkit-overflow-scrolling:touchする方法がありますが、これどちらを採用するか迷いませんか?>WEB制作者の皆様


これ一長一短がありますよねー?


■ヘッダ部をposition:fixedする方法
◯ 画面上部の時刻部分(iOSのステータスバー)をタップするとページトップに戻ることができる
◯ 一応レベルだけどAndroidでもそのまま使える
△ スライドメニューを出した場合、画面スクロールの制御が難しい
△ Pull to Refreshの実装が難しかった

■コンテンツ部をoverflow-y:scroll(-webkit-overflow-scrolling:touch)する方法
× ステータスバー(iOS)をタップしてもページトップに戻れない(※1)
× Android(2.0?)で動かない、見ることができない
◯ スライドメニューを出してもすっきり綺麗に動く
◯ Pull to Refreshモドキが作りやすい

追記2012.10.06
もう一つありました。ver0.0.2は以下の方法で実装してみました。

ヘッダ部をposition:absoluteしてコンテンツ部はtouchmoveイベントを元に独自スクロール
× ブラウザネイティブのスクロールの動きとはちょっと違う
○ ステータスバー(iOS)タップでトップにスクロールが実装可能
○ Pull to Refreshも実装可能
#Gmailのウェブアプリはこんな感じの実装っぽい


自分も色々と迷ったのですが、今回はコンテンツ部をoverflow-y:scrollする方法で実装してみました。
スライドして出てくるメニューもoverflow-y:scrollです。



あとウェブ特有の動きとして、スクロール部分がscrollTop()==0の時に下に引っ張るとbody全体(header分も含めて)下にズルッと下がっちゃうんですよね。これちょっとかっこ悪い。

それを阻止するために、スクロール部分は常にscrollTop(1)を保つようにしてます。
これでbodyやheaderはそのまま、スクロール部分のみ下に下がる。
そしてそのスクロールイベントを取得するとPull to Refresh実現につながります。


(※1)
ステータスバーをタップしてインナースクロール部分をトップに戻す為のギミックとして、
・bodyの高さをwindow.innerHeight+1にする
・常にwindow.scrollTo(0,1)するように
・ステータスバーをタップしてwindowスクロールイベントをトリガー
・window.scrollTop()==0になったらインナースクロール部分をトップまで戻す
・・・というのを考えて試してみたのですが、なんとコンテンツ内に -webkit-overflow-scrolling: touch しているエレメントがあると"ステータスバーのタップでスクロール"が効かなくなるという罠があるらしく、撃沈しました…(´・ω・`) 

まだまだ習作は続きそう…


通常のWEBだと、このくらいのプラグインで事足りそうなのですが、実は個人的に致命的な問題が…。
何故だかこのプラグインをPhoneGapでラップして使うと、よくわからないエラーでアプリが落ちてしまうのです><
多分overflow-yが原因だと思うのですが…Cordovaのバージョンがちょっと古いせいなのかなー。どなたか偉い人教えて頂けると嬉しいです(´・ω・`)

自分としてはPhoneGapでも使うつもりだったので、解決しなければoverflow-yを使わない方法で書きなおそうと思っています…てなわけで続く。



#追記 2012.10.05
overflow-y:scroll、-webkit-overflow-scrolling:touchを使用しないバージョンを作ってPhoneGapで実行してみたのですがステータスバーをタップするとやはり時々アプリが落ちる…
Cordovaのバージョンが怪しくなって来ました。。。むむぅ
PhoneGapをバージョンアップしたら解決しました。

[WP] Ktai Entryで画像つきメール投稿ができなくなる問題


 WordPressでお世話になっている Ktai Entry プラグイン。
作者のゆりこさんには感謝感謝です。

さて、Ktai Entryを使っていてある日なぜか全く投稿されなくなってしまいました。
WordPressの管理画面から「すぐにメッセージを読み出す」しても
There is * messages.
とでるだけ。
本文だけは下書きとして残ってる状態。

原因を追ってみると、どうやらJpegファイルが添付されているのにメールbodyのmimeがimage/pngになっている場合にエラーを吐いて停止している模様。

とりあえずpost.phpを書き換えて画像の拡張子とMIMEタイプが違ってもスルーさせたらOKでした。
post.php 925行目あたり、save_image関数内で以下をコメントアウト
/*
if (strtolower($type) != $mimetype) {
@unlink($filepath);
throw new KE_Error(sprintf(__('Invalid image type "%1$s" for file: %2$s', 'ktai_entry_log'), $mimetype, $filepath));
}
*/
他にもmemory_limitが少なくて止まるケースもあるみたい。
サーバログなどもチェックしましょう。