0

Androidのギャラリーの「共有」メニューから呼び出せるアプリを作る

Androidのカメラで撮影した画像はギャラリーに保存される。そこで画像を選んで共有またはshareなどを選択すると、暗黙的Intent呼び出しで画像を別アプリに渡せる。
Flickrアップローダとか、EvernoteとかDropboxのアプリに写真を渡しているのはこのしくみを使っている。


まずManifest.xmlにintent-filterを追加する。
image/*でもいいが、それだと動画も渡せてしまうので手当たり次第mimeTypeを指定してみる。

<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/jpeg"/>
<data android:mimeType="image/jpg"/>
<data android:mimeType="image/png"/>
<data android:mimeType="image/bmp"/>
<data android:mimeType="image/bitmap"/>
</intent-filter>
ようするに、アプリに「私はimageをSENDするというアクションを受け取って適当に処理できますよ」と宣言させておくと、
他のアプリが「誰でもいいからSENDしたimageを受け取ってくれる人いませんか」と呼び出したときに連携できるようになる。


で、このintent-filterと関連付けられているクラス(普通なら一番最初に生成したjavaクラス)の
onCreate()内でintentを受け取る。
あらかじめ、受け取った画像を表示するためのImageViewを配置しておく。
private ImageView imageView;
this.imageView = (ImageView)this.findViewById(R.id.ImageView01);


暗黙的Intentで呼び出されたのか、普通のIntent呼び出しで起動したのか(つまりホーム画面からアプリのアイコンを押して起動したのか)を判別して処理する。
呼び出し元の名前だけで判別してる
Uri imageUri = null;
try{
imageUri = Uri.parse(getIntent().getExtras().get("android.intent.extra.STREAM").toString());
}
catch(Exception e){
e.printStackTrace();
}
if (imageUri != null) {
Log.v("ImageIntent", "暗黙的intentから起動");
Bitmap bmp = null;
try {
bmp = Media.getBitmap(getContentResolver(), imageUri);
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
if (bmp != null) {
this.imageView.setImageBitmap(bmp);
}
}
else{
Log.v("ImageIntent", "普通に起動");
}
imageUriにはcontent://media〜〜というパスが入る。これはAndroid内のファイルシステムのパスではないが、Media.getBitmapに渡すとデータを読み出せる。
とりあえずImageViewに表示だけしておく。


あとEclipseだと日本語が化けるのでterminalからadb logcatしてる。


Bitmapを受け取ったら、後は適当に処理すれば良い。
リサイズとか
public Bitmap resize(Bitmap bmp, float scale){
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
}



form/multipart-dataでgyazo.comにアップロードするとか、色々できる。
Manifest.xmlにpermissionを追加
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
public void upload(Bitmap bmp) throws ParseException, IOException{
File dataDir = new File(Environment.getExternalStorageDirectory(), this.getPackageName());
dataDir.mkdirs();
FileOutputStream tmp = new FileOutputStream(new File(dataDir, "tmp.png"));
bmp.compress(Bitmap.CompressFormat.PNG, 80, tmp);
tmp.close();

HttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost("http://gyazo.com/upload.cgi");
MultipartEntity entity = new MultipartEntity();

entity.addPart("id", new StringBody("your-gyazo-id"));
entity.addPart("imagedata", new FileBody(new File(dataDir, "tmp.png"), "image/png"));
post.setEntity(entity);
post.setHeader("User-Agent", "TestAndroidApp/0.1");

httpClient.execute(post);
}
httpでform/multipart-dataでアップロードするには
の2つが必要。


■参考
このへんが参考になった

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;
        }
    }

}


■参考

0

gemだけで画像をリサイズできるImageResizeを作った

ImageResizeはImageMagickなどの外部プログラムに依存せずに、単体で画像をリサイズできる。ただしJavaの実行環境が必要。


SFCの「革新的ネットサービスの構築」という授業のTAをやっていて、昨日と今日niftyに行って開発合宿をしていた。
そこでいろいろあって、ImageMagickなしで画像のサムネイルを作る方法が無いのか模索していたら、ImageResizeというgemができた。

■インストール

gem install ImageResize


■使う
require 'rubygems'
require 'ImageResize'

# input, output, width, height
Image.resize('big.jpg', 'small.jpg', 40, 40)
これで40×40ピクセルに縮小される。
縦横のアスペクト比が1:1ではない画像の場合、アスペクト比を保ったまま長辺の方にあわせて縮小する。


■ソースコード
githubに置いた。
http://github.com/shokai/ImageResize-ruby


■実装
本体はJava。
標準実行環境にJPEGやGIFやBITMAPなど様々な画像フォーマットの読み書き機能を含んでいる環境というと、Javaと.NETとopenFrameworksしか思いつかなかった。
その中でいちばん色々なマシンにインストールされていそうで、配布が容易な物を選んだらJavaになった。


Javaでの画像の扱いは.NET並に簡単。java.awtとjavax.imageioの下に色々充実しているのでそれらを使えばいい。
ImageResize.java

import javax.imageio.*;
import java.io.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.regex.*;

class ImageResize{

    public ImageResize(){
    }

    public static void main(String args[]){
if(args.length < 4){
    System.out.println("ImageResize in.jpg out.jpg 320 320");
    System.exit(1);
}
ImageResize app = new ImageResize();
if(app.resize(args[0], args[1], Integer.parseInt(args[2]), Integer.parseInt(args[3]))){
    System.out.println(args[1]);
}
    }

    public boolean resize(String fname_in, String fname_out, int max_width, int max_height){
System.out.println(fname_in);
BufferedImage img = null;
try {
    img = ImageIO.read(new File(fname_in));
}
catch (Exception e) {
    e.printStackTrace();
    img = null;
}

int width, height;
if(img.getWidth() < img.getHeight()){
    height = max_height;
    width = img.getWidth() * max_height / img.getHeight();
}
else{
    width = max_width;
    height = img.getHeight() * max_width / img.getWidth();
}
System.out.println(img.getWidth() + "x" +img.getHeight() + " => " + width + "x" + height);

BufferedImage img_resized = new BufferedImage(width, height, img.getType());
AffineTransformOp ato = new AffineTransformOp(AffineTransform.getScaleInstance((double)width / img.getWidth(), 
       (double)height / img.getHeight()), 
      null);
ato.filter(img, img_resized);


String format = Pattern.compile("^.+\\.(.+)$").matcher(fname_out).replaceAll("$1");
boolean result = false;
try {
    result = ImageIO.write(img_resized, format, new File(fname_out));
}
catch (Exception e) {
    e.printStackTrace();
    result = false;
}
return result;
    }
    
}


■参考


今回は使わなかったが、Java Advanced Imaging (JAI) APIというのが標準実行環境には含まれていないけどSunが作っていて、かなり充実しているらしい。
Javaランタイムも速くなってきているらしいしScalaで画像処理とかしてみたい。