0

JRubyでglitch iconを作る(2)

プラグイン機構を採用し、ランダムにいろいろ作れるようにしてみた。
普通はjpegとかのバイナリを直接いじるみたいだけどよく分からないのでJRubyでjavax.imageioを使ってやっている。

ランダムに96個作ってみて8×12に敷き詰めてみたのがこれ。
で、定期的にランダムに作ってtwitterのアイコンとしてアップロードしてる
montage96
敷き詰めるのはImageMagickと一緒にインストールされるmontageコマンドでできる montageコマンド – 橋本詳解

ソースコードは全てgithubに置いた


■仕組み
単機能モジュールをランダムに連結する事である程度ランダムな画像を生成できる。
javax.imageio.BufferedImageのインスタンスを受け取り、少し加工して返すというJRubyのmoduleをプラグインとし、それらをGlitchというclassがランダムに呼び出す。


今のところプラグインは23種類ある。単体だとそれほど派手にはならない。
glitchicon montage



■プラグイン
http://github.com/shokai/glitchicon/tree/master/plugins/にある。
pluginが動くルールはこれ。

  • pluginsディレクトリの中に置いておく
  • ファイル名末尾が.rbである
  • JRubyのmoduleである
  • ファイル名の先頭1文字を大文字にしたmodule名である
  • BufferedImageを受け取って、同じサイズのBufferedImageを返す glitch(BufferedImage) というstatic methodを持つ
  • 受け取ったBufferedImageに対しては破壊的に処理しても、そうでなくてもいい

プラグインはBufferedImageの処理に集中できるように、他の事はglitch.rbがやる。


どのプラグインも簡単にできている。30行ぐらい。

drumroll_verticalプラグインの場合(画像では一番左、下から2番目)
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Drumroll_vertical

  def Drumroll_vertical.glitch(img)
    img_result = BufferedImage.new(img.width, img.height, img.type)
    roll = 0
    for x in 0...img.width do
      roll = rand(img.height) if rand > 0.95
      roll = 0 if rand > 0.95
      for y in 0...img.height do
        pix = img.get_rgb(x, y)
        y2 = y+roll
        y2 -= img.height if y2 > img.height-1
        img_result.set_rgb(x, y2, pix)
      end
    end
    return img_result
  end

end
ランダムに縦に区切って、ランダムにスロットのドラムみたいにずらす。


色を反転させるcolor_reverseプラグイン
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Color_reverse

  def Color_reverse.glitch(img)
    for y in 0...img.height do
      for x in 0...img.width do
        pix = img.get_rgb(x, y)
        r = pix >> 16 & 0xFF
        g = pix >> 8 & 0xFF
        b = pix & 0xFF
        r = 256-r
        g = 256-g
        b = 256-b
        pix = ((r << 16)&0xFF0000 | (g << 8)&0xFF00 | b)
        img.set_rgb(x,y, pix)
      end
    end
    return img
  end

end


量子化した後に輪郭抽出するquantize_contour
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Quantize_contour

  def Quantize_contour.glitch(img)

    for y in 0...img.height do
      for x in 0...img.width do
        pix = img.get_rgb(x, y)
        r = pix >> 16 & 0xFF
        g = pix >> 8 & 0xFF
        b = pix & 0xFF
        gray = (r+g+b)/3
        quant = gray & 0xC0
        pix = ((quant << 16)&0xFF0000 | (quant << 8)&0xFF00 | quant)
        img.set_rgb(x, y, pix)
      end
    end

    img_result = BufferedImage.new(img.width, img.height, img.type)
    for y in 1...img.height-1 do
      for x in 1...img.width-1 do
        pix = img.get_rgb(x, y)
        around = (img.get_rgb(x-1,y)+img.get_rgb(x+1,y)+img.get_rgb(x,y-1)+img.get_rgb(x,y+1))/4
        if around < pix
          pix = 0
        else
          pix = 0xFFFFFF
        end
        img_result.set_rgb(x, y, pix)
      end
    end
    return img_result
  end

end


そんなかんじ。画像をいじるけど、元の画像を知っている人なら元画像を思い浮かべられる程度にglitchしたい。

0

JRubyでglitch iconを作る

こういうのをtwitter iconにしたかった


Twitter のアイコンの目を光らせるライフハック – 地獄の猫日記を参考にアニメgifを作った。末尾も3C2Cにした。

でもちょうど今日からtwitterがGIFアニメアイコンのチェックを厳しくしたらしく、最初の1フレームだけの静止アイコンになってしまう。
既にアップされている他の人のアイコンを見ると、3C00002Cとかになってるのもあったけど色々ためしたけどやっぱりアップロードすると静止画像になる。


画像を生成するスクリプト
jrubyでjavax.imageioを使うと簡単。
最後のあたりのコメントアウトしているconvertかffmpegどちらでも、アニメーションgifを作れる。
glitchicon.rb

#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'

if ARGV.size < 2
  STDERR.puts 'require : input file and output dir'
  STDERR.puts 'jruby glitchicon.rb /path/to/img.jpg /path/to/out/'
  exit 1
end

img_in = ImageIO.read(java.io.File.new(ARGV.shift))
out_dir = ARGV.shift
out_dir += '/' unless out_dir =~ /\/$/

puts "#{img_in.width}x#{img_in.height}"

for i in 1..10
  img = BufferedImage.new(img_in.width, img_in.height, img_in.type);
  img.graphics.drawImage(img_in, 0, 0, nil)
  shifts = [0,0,0]
  for y in 0...img.height do
    if rand > 0.85
      shifts = shifts.map{|j|
        j = rand(255)-128
      }
    end
    for x in 0...img.width do
      pix = img.get_rgb(x, y)
      r = pix >> 16 & 0xFF
      g = pix >> 8 & 0xFF
      b = pix & 0xFF
      r += shifts[0]
      g += shifts[1]
      b += shifts[2]
      r = 255 if r > 255
      g = 255 if g > 255
      b = 255 if b > 255
      r = 0 if r < 0
      g = 0 if g < 0
      b = 0 if b < 0
      pix = ((r << 16)&0xFF0000 | (g << 8)&0xFF00 | b)
      "#{r},#{g},#{b}"
      img.set_rgb(x,y, pix)
    end
  end
  out_name = "#{out_dir}#{i}.bmp"
  ImageIO.write(img, 'bmp', java.io.File.new(out_name))
  puts out_name
end

puts `convert #{out_dir}*.bmp #{out_dir}out.gif`
#puts `ffmpeg -y -i #{out_dir}%d.bmp -s 100x100 -pix_fmt rgb24 -loop_output 0 -sameq #{out_dir}out.gif`

open("#{out_dir}out.gif"){|gif|
  bytes = gif.read.unpack('H*')
  bytes = [bytes.first.gsub!(/3b$/,'3c2c')]
  open("#{out_dir}glitchicon.gif",'w+'){|out|
    out.write(bytes.pack('H*'))
  }
}

0

PSoC – CY8C29466の14ビットADコンバータを内蔵マルチプレクサで切り替えて複数ピンで使う

14ビットのADコンバータ1つと、AMUX4(4チャンネルアナログマルチプレクサ)を組み合わせると複数のピンからAD変換が使える。
というかPSoC Designer4.xでADCINC12を使っていた頃は、ADCINC12を複数置くことができたのにPSoC Designer5.0では1つしか配置できなくて困った。
でもよく考えたらマルチプレクサ使う方が自然だな。


アナログブロックの接続
マルチプレクサを切り替えるとPort0の0,2,4,6番ピンに接続できる
cfab931ebc6fd7a15179e8377e1a847c


ピン配置
右上のADC以外は関係ない
e9c55cbf49c94421f1ef6f24400f6531


PGAの設定
1.0倍で、AMUXがあるアナログマルチプレクサ1に接続する。
e838b8bcea333aaa21f7249301632629


ADCINCの設定
Negative Inputのソースは一応指定するが、GainをDisconnectedにすると使わないようにできる。
5f959013ed6c5d34154921a08c1cf097


AMUX4の設定
Interconnect Viewには表示されないけど、配置はされてる。アナログマルチプレクサ1として設定する。
ff18fb9db0458d481244adfc38113cfb


AMUX4の他にも8もあるので、まあたくさん増やせる。


UARTのTX8で、14bitADCINCの値を送る。
TX8だけを使う設定は橋本商会 ≫ PSoC – TX8モジュールに書いた

void wait(int n){
    while(n--);
}

// AMUX4_PORT0_0 => 0x00
// AMUX4_PORT0_2 => 0x01
// AMUX4_PORT0_4 => 0x02
// AMUX4_PORT0_6 => 0x03
int get_adc(BYTE amux_channel){
    AMUX4_InputSelect(amux_channel);
    wait(10);
    ADCINC_GetSamples(0);
    while(!ADCINC_fIsDataAvailable());
    return ADCINC_iClearFlagGetData();
}

int ad;
BYTE ad_pin;
char buf[6];

void main(void){
    M8C_EnableGInt;
    AMUX4_Start();
    PGA_1_Start(PGA_1_HIGHPOWER);
    ADCINC_Start(ADCINC_HIGHPOWER);
    TX8_Start(TX8_PARITY_NONE);

    for(;;){
        for(ad_pin = 0; ad_pin < 4; ad_pin++){
            ad = get_adc(ad_pin);
            TX8_PutChar(ad_pin+'0');
            TX8_CPutString(":");
            TX8_PutString(intToStr(ad,buf));
            TX8_PutCRLF();
        }
    }
}
AMUX4を切り替えた直後にAD変換を開始するのがなんとなく嫌だった(物理的に回路が切り替わっているわけだし)ので、一瞬waitを入れてからAD変換するようにした。



string.hのitoa関数がおかしいので代わりを作った
// intの桁数を返す
char getDigit(int n){
    char i;
    i = 0;
    while(n>0){
        n /= 10;
        i++;
    }
    return i;
}

// int->String変換
// char buf[6]
char *intToStr(int n, char *buf){ // 変換する数、作業領域
    int i, digit;
    digit = getDigit(n); // 桁数
    for(i = digit-1; i >= 0; i--){ // intは最大5桁
        buf[i] = n%10+'0';
        n /= 10;
    }
    buf[digit] = '\0'; // 行末
    return buf;
}

0

OpenCVで画像サイズを求めるgearman workerをdaemontoolsで管理する

OpenCVで画像のサイズを求めるgearman workerを作って、Rubyから呼ぶで作ったworkerをpreforkさせて、そいつらをdaemontoolsで管理できるようにした。あらかじめCPU個数+いくつかforkしておくと、CPUが複数あるマシンを生かせるし、解析前にlibcurlで画像を取得している時のI/O待ちが少なくなって良い。(この記事のworkerはlibcurl使ってない版だけど)
あと、返り値は自分で作ったjson_builder.hを使って返すようにした。

なにげに大量の画像の中からダウンロード失敗した破損画像を見つけるのに重宝している。

まずdaemontoolsをインストールしておく

gearmandもdaemontoolsで自動起動するようにしておく。


daemontoolsで管理できるようにする。
普通にforkしただけだと、daemontoolsでsvc -dしてプロセスを止めようとしてもforkした子プロセスの方が止まらない。

Perlの場合の良い例があった。
How to manage Gearman worker processes. – TokuLog 改メ tokuhirom’s blog
Parallel::Preforkを使っている。Parallel::Preforkのソースを読んでみたら、trap_signalsオプションで親プロセスがSIGTERMとSIGHUPをフックして、子プロセスにkillを送っていた。
よく考えたら普通のforkで親が子を殺すというやつだった。


Parallel::Preforkと同じ様にやる。
forkした後親が子のpidのリストを持っておいて、SIGTERM/SIGHUPをフックして、子を全部killする処理を追加した。

daemontoolsのrunスクリプトはこれ
#!/bin/sh
exec 2>&1
exec setuidgid sho /Users/sho/src/gearmand-study/imgsize/imgsizeWorker -s localhost -p 7003 --fork 5
起動すると5個にプロセスが増える。親はdaemontoolsのsuperviseが管理してくれる。
これでsvc -dとか-uとかすればまとめて起動終了するようになった。

imgsizeWorker.cpp
// 画像サイズを返すgearman worker
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/program_options.hpp>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/any.hpp>
#include <libgearman/gearman.h>
#include "json_builder.h"

using namespace boost;
using namespace std;

tuple<int, int> get_size(const string& fileName); // 画像のwidth,heightを返す
map<string,any> imgsize(const string& fileName); // gearman workerとしてclientに返すためのJSON Objectを作る
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);
void on_exit_signal(int sig);
vector<int> pids;

int main(int argc, char* argv[]) {
  program_options::options_description opts("options");
  opts.add_options()
    ("help,h", "helpを表示")
    ("server,s", program_options::value<string>(), "gearmanサーバーのアドレス")
    ("port,p", program_options::value<int>(), "gearmanサーバーのport番号")
    ("fork", program_options::value<int>(), "preforkする数")
    ("test,t", program_options::value<string>(), "gearman worker単体テスト用query");
  program_options::variables_map argmap;
  program_options::store(parse_command_line(argc, argv, opts), argmap);
  program_options::notify(argmap);

  if(!argmap.count("help")){
    if(argmap.count("test")){
      cout << "---test---" << endl;
      string gearman_param = argmap["test"].as<string>();
      cout << json_builder::toJson(imgsize(gearman_param)) << endl; // 単体でworkerとしてのテスト
      return 0;
    }else if(argmap.count("server") && argmap.count("port")){
      if(argmap.count("fork")){
int i, pid;
for(i = 1; i < argmap["fork"].as<int>(); i++){
  pid = fork();
  if(pid == 0){ // 子プロセス
    pids.clear();
    break;
  }
  else{ // 親プロセス
    pids.push_back(pid);
    cout << str(format("fork:%d - parent:%d child:%d") % 
i %
getpid() %
pid) << endl;
  }
}
      }
      if(pids.size() > 0){ // 親プロセスの終了シグナルをフックする
signal(SIGTERM, on_exit_signal);
signal(SIGHUP, on_exit_signal);
      }
      gearman_worker_st worker;
      gearman_worker_create(&worker);
      string g_server = argmap["server"].as<string>();
      int g_port = argmap["port"].as<int>();

      struct hostent *g_host = gethostbyname((char*)g_server.c_str());
      string g_server_addr = str(format("%d.%d.%d.%d") %
 (uint)(uchar)g_host->h_addr[0] %
 (uint)(uchar)g_host->h_addr[1] %
 (uint)(uchar)g_host->h_addr[2] %
 (uint)(uchar)g_host->h_addr[3]);

      gearman_worker_add_server(&worker, g_server_addr.c_str(), g_port);
      gearman_worker_add_function(&worker, "img_size", 0, job_imgsize, NULL);
      cout << str(format("---start worker (%s:%d)---") %
  g_server_addr % g_port) << endl;
      while(true) gearman_worker_work(&worker); // workerとして待機
      return 0;
    }
  }
  cerr << "server,portが必要です" << endl;
  cerr << opts << endl;
  return 1;
  
}

// opencvで画像サイズを取得
tuple<int, int> get_size(const string& fileName){
  IplImage *img = cvLoadImage(fileName.c_str());
  if(!img){
    return make_tuple(-1, -1);
  }
  else{
    int width = img->width;
    int height = img->height;
    cvReleaseImage(&img);
    return make_tuple(width, height);
  }
}

// 画像サイズを取得してgearman serverに返すJSON Objectを作る
map<string,any> imgsize(const string& fileName){
  map<string,any> result_m;
  int width, height;
  tie(width, height) = get_size(fileName);
  if(width > 0 && height > 0){
    result_m["width"] = width;
    result_m["height"] = height;
  }
  else{
    result_m["error"] = string("image load error");
  }
  return result_m;
}

// gearman worker job
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
  string fileName = (char*)gearman_job_workload(job);
  cout << fileName << endl;
  string result_str = json_builder::toJson(imgsize(fileName));
  cout << " => " << result_str << endl;
  char *result = (char*)strdup(result_str.c_str());
  *result_size = result_str.size();
  *ret_ptr = GEARMAN_SUCCESS;
  return result;
}

void on_exit_signal(int sig){
  for(int i = 0; i < pids.size(); i++){
    cout << str(format("kill (pid:%d)") % pids[i]) << endl;
    if(kill(pids[i], SIGKILL) < 0){
      cerr << str(format("kill failed (pid:%d)") % pids[i]) << endl;
    }
  }
  exit(0);
}

1

Android – アプリの設定画面を作る

画面遷移するアプリを作ったこと無かったのでやってみた。
AndroidではIntentで別のActivityを呼び出して画面遷移する。遷移ついでにIntentオブジェクトに値を入れて渡したり、返り値を受け取ったりする。

作った物はgithubに置いた



■動作
名前設定ボタンを押したらIntentで別のActivityに遷移して、そこで名前を設定する。保存(終了)すると前のActivityに戻る。もう一度やると、設定ActivityのEditTextの中身がさっき保存した値になっている。
device device2 device3



■Activityを追加する
  1. android.app.Activityを継承した新しいJavaクラスを追加する。ConfigActivity.javaにした。
  2. res/layout/ の中にレイアウト用XMLファイルを作る。既にあるmain.xmlをコピー&リネームでいい。config.xmlにして中身を適当に編集
  3. レイアウトを.javaに読み込む。config.xmlを作った時点でresourceが更新されているので、onCreate関数の中で setContentView(R.layout.config); すればいい。
  4. AndroidManifest.xmlを編集、<application></application>の中に新しいActivity名を書く
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.shokai"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".MainActivity"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".ConfigActivity" android:label="@string/app_name">
            </activity>

        </application>
        <uses-sdk android:minSdkVersion="7" />

    </manifest> 
    名前は .java ファイルの名前に合わせる。
これでConfigActivityを呼び出す準備ができた。



■ソースコード
返り値が必要な場合はstartActivityForResultで呼び出す。第二引数の数値はrequest codeで、onActivityResultで返り値を受け取る時に同じ値が戻ってくるので本物かどうかチェックするのに使う。
request codeは毎回違う値を生成した方がいいのかもしれない
MainActivity.java
package org.shokai;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;

public class MainActivity extends Activity implements OnClickListener{
    
    private Button buttonOpen;
    private Logger logger;
    private TextView textViewName;
    private String name;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        logger = new Logger(this.getResources().getString(R.string.app_name));
        textViewName = (TextView)findViewById(R.id.TextViewName);
        buttonOpen = (Button)findViewById(R.id.ButtonOpen);
        buttonOpen.setOnClickListener(this);
    }

    public void onClick(View arg0) {
        switch(arg0.getId()){
        case R.id.ButtonOpen:
            logger.v("click : ButtonOpen");
            Intent it = new Intent(this, ConfigActivity.class);
            if(name != null) it.putExtra("name", name);
            this.startActivityForResult(it, 1234);
            break;
        }
    }
    
    protected void onActivityResult(int reqCode, int resCode, Intent it) {
        logger.v("responseCode:"+resCode+", requestCode:"+reqCode);
        switch(reqCode){
        case 1234:
            setName(it.getStringExtra("name"));
            break;
        }
    }
    
    private void setName(String name){
        this.name = name;
        this.textViewName.setText(name);
    }
}


onCreate関数内でgetIntent()すると、MainActivityから呼び出したIntentオブジェクトが手に入る。この中から値を取り出す。
Intent.getStringExtra(key) も Intent.getExtras().getString(key) も同じ意味の関数だが、getExtras().getString(key) に存在しないkeyを与えると強制終了するので getStringExtra(key) の方を使った。こちらはnullが返ってくる。
finish()するとMainActivityに戻るが、戻る時にsetResultしておいて値を戻す。setResultは終了コード的な意味で、MainActivityのonActivityResultの第二引数に入る。
ConfigActivity.java
package org.shokai;

import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.os.Bundle;
import android.widget.*;

public class ConfigActivity extends Activity implements OnClickListener{

    private EditText editTextName;
    private Button buttonSave;
    private Logger logger;
    
    @Override
    public void onCreate(Bundle bundle){
        super.onCreate(bundle);
        logger = new Logger(this.getResources().getString(R.string.app_name));
        setContentView(R.layout.config);
        editTextName = (EditText)findViewById(R.id.EditTextName);
        buttonSave = (Button)findViewById(R.id.ButtonSave);
        buttonSave.setOnClickListener(this);
        Intent it = getIntent();
        String name = it.getStringExtra("name");
        if(name != null) editTextName.setText(name);
    }

    public void onClick(View v) {
        switch(v.getId()){
        case R.id.ButtonSave:
            logger.v("click : ButtonSave");
            Intent it = new Intent();
            it.putExtra("name", this.editTextName.getText().toString());
            this.setResult(0, it);
            this.finish();
            break;
        }
    }

}


■参考