エントリー

カテゴリー「プログラム」の検索結果は以下のとおりです。

Android版GPUImageで画像加工

画像や動画にエフェクトをかけることができるライブラリである、GPUImageをAndroidから使ってみたのでメモ。

サンプル実行

NDK(Android Native Development Kit)のインストールが必要

GradleでNDKを自動インストールしてくれるプロジェクトもあるが、GPUImage for Android のサンプルはそうなっていない。Android Studio で

ファイル → 設定 → SDK Tools → NDK

にチェックを入れて適用すると、NDKがインストールされる。インストールされたら

C:\Users\ユーザ名\AndroidStudioProjects\GPUImage\local.properties

ndk.dir=C\:\\Users\\ユーザ名\\AppData\\Local\\Android\\Sdk\\ndk-bundle

のパスが正しいことを確認する。

さらに

C:\Users\ユーザ名\AndroidStudioProjects\GPUImage\library\build.gradle

の12行目を編集。(gitからバージョンコードを取得している?ようだが、取れずにエラーになるので適当な数値を設定)

versionCode "git rev-list origin/master --count".execute().text.toInteger()
↓
versionCode 1

まだ謎エラーが出るようなら、

ビルド → Clean Project
ビルド → Rebuild Project

を試す。問題がなくなると追加ライブラリのインストールを求められたのでインストール。その後実行すると、サンプルを実機にインストールできた。

プロジェクトに組み込み

C:\Users\ユーザ名\AndroidStudioProjects\プロジェクト名\app\build.gradle

のdependenciesに以下を追加

//GPUImage
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.1'

これだけでプロジェクトにGPUImageが組み込まれるので、あとは呼び出すだけ。以下Intentから画像を選択して、その結果にフィルタをかけて表示するサンプルの抜粋。

// Intentを取得
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, REQUEST_GALLERY);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_GALLERY && resultCode == RESULT_OK) try {
        InputStream in = getContentResolver().openInputStream(data.getData());
        Bitmap img = BitmapFactory.decodeStream(in);
        in.close();

        // GPUImageで画像を加工
        GPUImage mGPUImage = new GPUImage(this);
        mGPUImage.setFilter(new GPUImageSobelEdgeDetection());
        mGPUImage.setImage(img);
        img = mGPUImage.getBitmapWithFilterApplied();

        // 画像を表示
        imageViewer.setImageBitmap(img);

        Toast.makeText(MainActivity.this, "画像を選択しました", Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

AndroidでHTTP通信

2年くらい前にAndroidをさわったときは org.apache.http でHTTPリクエストしていたけど、今は使えなくなっているらしい。

どうやら、OkHttpというのを使うのがイマドキらしい。ので試してみた。

確かに org.apache.http を使うよりも、超シンプルなコードになるのでいい感じ。以前作った org.apache.http を簡単に扱えるようにするクラスが用済みになってしまったけど、無事に通信できたので良しとする。

以下、上に書かれた内容と同じだけど自分の作業メモ。

compile 'com.squareup.okhttp:okhttp:2.5.0'

でライブラリを取り込んで、マニフェストファイルで

<uses-permission android:name="android.permission.INTERNET" />

を指定して、以下のコードで通信テスト。リソースファイルは略。

package org.refirio.request;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.IOException;

public class MainActivity extends Activity {

    private TextView message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        message = (TextView)this.findViewById(R.id.message);

        // データ取得(GET)
        Button button_get = (Button)this.findViewById(R.id.button_get);
        button_get.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                new AsyncTask<Void, Void, String>() {
                    @Override
                    protected String doInBackground(Void... params) {
                        String result = null;

                        // リクエストオブジェクトを作って
                        Request request = new Request.Builder()
                                .url("http://httpbin.org/headers")
                                .get()
                                .build();

                        // クライアントオブジェクトを作って
                        OkHttpClient client = new OkHttpClient();

                        // リクエストして結果を受け取って
                        try {
                            Response response = client.newCall(request).execute();
                            result = response.body().string();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        // 返す
                        return result;
                    }

                    @Override
                    protected void onPostExecute(String result) {
                        message.setText("Result: " + result);
                    }
                }.execute();

            }

        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Google App EngineでPHPを動作させるメモ

Google App Engineを少しだけ触ってみたのでメモ。

参考サイト

参考にはなったけど、色々端折られた個人メモみたいなので、自分用に改めてメモ。

環境構築

あらかじめWindowsに、Python2.7.xをインストールしておく。(GAEのツール自体がPythonで書かれているため、PHPを動作させる場合でも必要。)

プロジェクト作成

https://console.developers.google.com/project から、プロジェクト名「GAE Test Project」、プロジェクトID「refirio-php-test」で新規にプロジェクトを作成する。(プロジェクト名とプロジェクトIDは任意のものを指定する。)

次に https://console.developers.google.com/project/refirio-php-test/start/appengine にアクセスして解説に従う。

Google App Engine SDK for PHP をインストールし、サンプルコードをダウンロードする。サンプルコードは C:\localhost\home\gae\public_html\refirio-php-test とか、ローカルの適当なディレクトリ内に配置する。

次にサンプルコードを編集する。.playground の内容は以下のとおり。

{
  "project_name": "PHP TEST - refirio",
  "project_description": "Starter code for a Google App Engine app in PHP.",
  "show_files": [
    "helloworld.php",
    "app.yaml"
  ],
  "read_only_demo_url": "https://refirio-php-test.appspot.com/",
  "download_filename": "appengine-try-php.zip"
}

app.yaml の内容は以下のとおり。

application: refirio-php-test
version: 1
runtime: php55
api_version: 1

handlers:
- url: /.*
  script: helloworld.php

helloworld.php の内容は以下のとおり。

<?php

echo 'Hello, GAE!';

公開

デスクトップに作成されたショートカットから「Google App Engine Launcher」を起動する。ここからローカル環境でのテスト実行や、本番環境へのデプロイができる。

デプロイが完了すると、以下のURLからアクセスできる。

Basic認証設定ツール

100番煎じどころではないと思われるツールを作ったので置いておきます。

Basic認証をかけたいディレクトリ内に basic.php などの名前でサーバにアップロードしてアクセスすると、作るべき .htaccess.htpasswd の場所と内容を表示します。先頭に並んでいる test1 とかの部分は、認証させるユーザ名とパスワードです。適当に変更&増減できます。

.htaccess の雛形を作ってくれたり .htpasswd の内容を作ってくれたりするツールは多いけど、AuthUserFile の内容を調べたりするのも面倒なのでそれも自動で。_(:3 」∠ )_

<?php

$users = array(
  'test1' => '1234',
  'test2' => '2345',
  'test3' => '3456',
);
$path = dirname($_SERVER['SCRIPT_FILENAME']);

?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Basic Authentication</title>
</head>
<body>
<h1>Basic Authentication</h1>
<h2><?php h($path) ?>/.htaccess</h2>
<pre>AuthUserFile <?php h($path) ?>/.htpasswd
AuthGroupFile /dev/null
AuthName "Basic"
AuthType Basic
require valid-user
&lt;Files ~ "^.(htpasswd|htaccess)$"&gt;
deny from all
&lt;/Files&gt;</pre>
<h2><?php h($path) ?>/.htpasswd</h2>
<pre><?php

foreach ($users as $username => $password) {
  t($username . ':' . crypt($password) . "\n");
}

?></pre>
</body>
</html>
<?php

function h($data)
{
  $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
  $data = nl2br($data);

  echo $data;
}
function t($data)
{
  $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');

  echo $data;
}

?>

Webアプリケーションへの同時アクセス対策メモ

Webアプリケーションへの同時アクセス対策メモ

XAMPPの Apache Bench で同時アクセスを再現するメモ。テストした環境はWindows7。Apache Bench の使い方は以下を参考に。

XAMPPをCドライブ直下にインストールした場合、以下の手順で Apache Bench を使える。

> cd C:\xampp\apache\bin
> ab -n 15 -c 15 http://localhost/~test/ab/

「-n 15 -c 15」で「15人が同時にアクセスする(合計15回アクセス)」状況がテストできる。(「-n 15 -c 1」なら「1人が合計15回アクセスする」状況になるので、同時アクセスにはならない。)

以下で同時アクセスのテストとその対策を行う。

ファイルを使った例

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

if ($fp = fopen('logs/count.log', 'r+')) {
  $data = '';
  while ($line = fgets($fp)) {
    list($id, $count) = explode("\t", rtrim($line));

    if ($id == 2) {
      if ($count < 100) {
        $data .= "$id\t" . ++$count . "\n";
      } else {
        exit('Limit');
      }
    } else {
      $data .= $line;
    }
  }

  rewind($fp);
  fwrite($fp, $data);
  ftruncate($fp, ftell($fp));

  fclose($fp);
} else {
  exit('Error');
}

exit('Complete');

logs/count.log の内容は以下のとおり。

1	10
2	20
3	30
4	40
5	50

これで上のPHPプログラムにアクセスすると、2の行が1カウントアップされる。何度もリロードすると、その回数だけカウントアップされる。

> ab -n 30 -c 30 http://localhost/~test/ab/count.php

30人の同時アクセスを発生させてみると、正しくカウントできないのが確認できる。

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

if ($fp = fopen('logs/count.log', 'r+')) {
  flock($fp, LOCK_EX);  //ファイルロック

  $data = '';
  while ($line = fgets($fp)) {
    list($id, $count) = explode("\t", rtrim($line));

    if ($id == 2) {
      if ($count < 100) {
        $data .= "$id\t" . ++$count . "\n";
      } else {
        exit('Limit');
      }
    } else {
      $data .= $line;
    }
  }

  rewind($fp);
  fwrite($fp, $data);
  ftruncate($fp, ftell($fp));

  fclose($fp);
} else {
  exit('Error');
}

exit('Complete');

ファイルロックを行えば、正しくカウントされるようになる。

データベースを使った例

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

try {
  $pdo = new PDO('mysql:dbname=test;host=localhost', 'root', '1234', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
  $pdo->query('SET NAMES utf8');

  $stmt = $pdo->prepare('SELECT count FROM count_test WHERE id = :id');
  $stmt->bindValue(':id', 2, PDO::PARAM_INT);
  $stmt->execute();

  $data = $stmt->fetch(PDO::FETCH_ASSOC);

  if ($data['count'] < 100) {
    $stmt = $pdo->prepare('UPDATE count_test SET count = count + 1 WHERE id = :id');
    $stmt->bindValue(':id', 2, PDO::PARAM_INT);
    $stmt->execute();
  }
} catch (PDOException $e) {
  exit($e->getMessage());
}

exit('Complete');

count_test の定義は以下のとおり。

CREATE TABLE count_test(
  id    INT UNSIGNED NOT NULL,
  count INT UNSIGNED,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

これで上のPHPプログラムにアクセスすると、2の行が1カウントアップされる。何度もリロードすると、その回数だけカウントアップされる。

ab -n 200 -c 200 http://localhost/~test/ab/count.php

200人の同時アクセスを発生させてみると、正しくカウントできないのが確認できる。

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

try {
  $pdo = new PDO('mysql:dbname=test;host=localhost', 'root', '1234', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
  $pdo->query('SET NAMES utf8');

  $pdo->beginTransaction();  //トランザクション開始

  $stmt = $pdo->prepare('SELECT count FROM count_test WHERE id = :id FOR UPDATE');  //行ロック
  $stmt->bindValue(':id', 2, PDO::PARAM_INT);
  $stmt->execute();

  $data = $stmt->fetch(PDO::FETCH_ASSOC);

  if ($data['count'] < 100) {
    $stmt = $pdo->prepare('UPDATE count_test SET count = count + 1 WHERE id = :id');
    $stmt->bindValue(':id', 2, PDO::PARAM_INT);
    $stmt->execute();
  }

  $pdo->commit();  //コミット
} catch (PDOException $e) {
  $pdo->rollBack();  //ロールバック

  exit($e->getMessage());
}

exit('Complete');

トランザクションと行ロックを行えば、正しくカウントされるようになる。

電話発信用のリンクをPCページで解除

スマートフォン向けページでは、tel: でリンクすれば電話をかけることができます。

<a href="tel:090-1234-5678">090-1234-5678</a>

ただしこの機能はPCサイトでは不要です。この対策に、以下のJavaScriptで電話番号のリンクを解除することができます。

var ua = navigator.userAgent;
if (ua.indexOf('iPhone') < 0 && ua.indexOf('Android') < 0) {
  $('a[href^="tel:"]').each(function() {
    $(this).contents().unwrap();
  });
}

処理の内容は「iPhoneでもAndroidでもなければ、リンク先が tel: が始まるリンクを解除する」としています。

Windows Phone も考慮するならuserAgentの判定は要改良ですが、ひとまずこれで。

Windows + コマンドプロンプト + PHPUnit でユニットテスト

今更ながらPHPUnitを試したのでメモ。

概要

PHPUnit3で始めるユニットテストが解りやすかったが情報が古い。でも、概要をつかむには良さそうです。

大まかな内容としては「◯◯の命令を△△の形式で呼び出して、□□の結果が返ってくればOK」というコードをたくさん書いたプログラムを準備しておき、それを実行すれば各命令が正しく実行されたかを知ることができるというもの。(プログラム作成中に不具合を作ってしまっても、即座に検知できる…かもしれない。)

大人数で各々が大量の部品を作るような開発では、上手く導入するととても活躍してくれそう。一人/少人数で作成しているようなプログラムでは、使いどころが難しいかも。(自分が解っていないだけかも。)

導入自体は簡単。

導入

自分の環境に合わせて「Windows+コマンドプロンプト+PHPUnit」でPHPプログラムのテストをしてみたので、手順をメモ。PHPUnit のインストールを主に参考にしています。

まずは公式サイトから phpunit.phar の安定版を入手する。

任意のディレクトリ内(C:\xxx\phpunit とする)に、入手した phpunit.phar を配置する。

C:\xxx\phpunit 内に phpunit.cmd を作成し、以下の内容を記述する。(PHPへのパスは環境に合わせて設定する。)

@C:\xampp\php\php.exe "%~dp0phpunit.phar" %*

コマンドプロンプトで C:\xxx\phpunit 内に移動し、以下のコマンドを実行。以下のようにバージョン情報が表示されれば成功。

C:\xxx\phpunit>phpunit --version
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

サンプルで動作確認

Getting Started with PHPUnit を参考にテスト。

上ページの内容を参考に C:\xxx\phpunit\src\Money.php (テストしたいクラス)を作成。次に C:\xxx\phpunit\tests\MoneyTest.php (テスト用プログラム)も作成。

コマンドプロンプトから、以下のようにコマンドを実行するとテストできる。

C:\xxx\phpunit>phpunit --bootstrap src/Money.php tests/MoneyTest
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

.

Time: 31 ms, Memory: 4.00Mb

OK (1 test, 1 assertion)

上のように表示されればテスト成功。

自作のプログラムで動作確認

PHPUnit 用のテストの書き方 を参考にテスト。

まずは C:\xxx\phpunit\src\calculate.php を作成し、掛け算と割り算の計算結果を返す命令を定義。これらの命令が正しく動作しているかテストするものとする。

<?php
function multiplication($x, $y) {
  return $x * $y;
}
function division($x, $y) {
  return $x / $y;
}

C:\xxx\phpunit\tests\calculate.php を作成し、各命令をテストするコードを書く。(計算結果が正しく返ってくればテスト成功とする。)

<?php
require_once 'C:/localhost/home/test/public_html/phpunit/src/calculate.php';
class Calculate extends PHPUnit_Framework_TestCase
{
    public function testCalculate()
    {
        $this->assertEquals(8, multiplication(4, 2));
        $this->assertEquals(2, division(4, 2));
    }
}

コマンドプロンプトからテスト実行。

C:\xxx\phpunit>phpunit tests/calculate
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

.

Time: 30 ms, Memory: 4.00Mb

OK (1 test, 2 assertions)

上のように表示されればテスト成功。試しに関数の内容を間違ったものにしてテストすると。

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

のような結果が表示されるので、正しくテストできていることが確認できる。

levis: PHP Framework

最近PHPでプログラムを書くときに時々使っているフレームワークを置いておきます。

有名どころのフレームワークを使うのが一番かと思っていたのですが、ちょっとしたことが簡単にできそうでできなかったり、フレームワークのバージョンアップに合わせてのメンテナンスが大変だったりなので。中小規模の制作だとオレオレフレームワークの方が小回りが利くのです。

名前の由来は、軽量フレームワークなのでラテン語の「levis」から。ちょっと中二病ネーミングです。意味は「軽い」で本来の読みは「レウィス」らしい。このフレームワークの名前としては、英語読みで「レヴィス」にしておきます。

使いながら結構いじっているので、仕様はまだ安定していません。

node.jsメモ

PHPでセッションハンドラを自作

PHPは標準機能でセッション管理ができますが、何故かセッション管理のみが正しく動作しないサーバーがあったので、セッションハンドラで対応してみた。

PHPでは標準命令の session_set_save_handler() を使用すれば、セッションを扱う際の挙動を自分で決めることができます。(セッションをMySQLで管理する仕組みを自作したり、など。)

以下はファイルでセッションを管理するサンプル。

<?php

define('SESSION_HANDLER_PATH', dirname(__FILE__) . '/sessions/');
define('SESSION_HANDLER_PREFIX', 'session_');
define('SESSION_HANDLER_EXTENSION', '.log');

if (SESSION_HANDLER_PATH != '') {
  $session_handler_path = SESSION_HANDLER_PATH;
} else {
  $session_handler_path = '';
}

function session_handler_open($save_path, $session_name) {
  global $session_handler_path;

  if ($session_handler_path == '') {
    $session_handler_path = $save_path;
  }

  return true;
}

function session_handler_close() {
  global $session_handler_path;

  return true;
}

function session_handler_read($session_id) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  if (file_exists($file)) {
    return file_get_contents($file);
  } else {
    return null;
  }
}

function session_handler_write($session_id, $data) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  return file_put_contents($file, $data);
}

function session_handler_destroy($session_id) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  unlink($file);

  return true;
}

function session_handler_gc($lifetime) {
  global $session_handler_path;

  if ($dir = scandir($session_handler_path)) {
    foreach ($dir as $entry) {
      if (is_file($session_handler_path . $entry) and preg_match('/^' . preg_quote(SESSION_HANDLER_PREFIX, '/') . '\w+' . preg_quote(SESSION_HANDLER_EXTENSION, '/') . '$/i', $entry) and time() - filemtime($session_handler_path . $entry) > $lifetime) {
        unlink($session_handler_path . $entry);
      }
    }
  }

  return true;
}

session_set_save_handler(
  'session_handler_open',
  'session_handler_close',
  'session_handler_read',
  'session_handler_write',
  'session_handler_destroy',
  'session_handler_gc'
);

?>

上のコードを一例ですが session_handler.php という名前で保存し、同じディレクトリ内に sessions というディレクトリを作成します。この中にセッションの情報が保存されます。

session_set_save_handler() 関数によって、「セッションを開始した時は session_handler_open() が呼ばれる」「セッションを保存する時は session_handler_write() が呼ばれる」のような設定を行っています。

以降は、以下のコードで自作のセッション管理を利用できます。

<?php

include_once('session_handler.php');
session_start();

if (isset($_SESSION['count'])) {
  $_SESSION['count']++;
} else {
  $_SESSION['count'] = 1;
}

?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>サンプル</title>
</head>
<body>
<?php

echo $_SESSION['count'] . '回目のアクセスです。';

?>
</body>
</html>

include_once('session_handler.php'); でファイルを読み込む以外は、ごく普通のセッション利用サンプルです。

急きょ書いたコードなので変な箇所があるかもしれません。また、名前の重複を避けるためにもクラス化した方が良かったかも。と思っていたら、公式サイトにクラスのサンプルがあった…。またそのうち調べる予定。

ページ移動

ユーティリティ

カテゴリー

検索

エントリー検索フォーム
キーワード

過去ログ

過去ログ表示フォーム
キーワード

Feed