AndroidのBitmap画像プログラミング(1) 矩形転送と矩形塗りつぶし
Androidで画像を扱う際に使用するBitmapクラスの使い方を様々なサンプルプログラムと共に解説します。
インターネット上で動的に画像を作成する手法があまり紹介されていません。
そこでAndroidのBitmap画像プログラミングの第一回は画像描画の手法を紹介します。
解説で使用するBitmapはARGBの32bitカラーです。
AndroidではBitmapクラスが画像の仕様に柔軟に対応しているのでヘッダー情報などは必要ありません。
intの配列からなるカラー情報を作成してやれば、簡単に画像を作成することができます。
画像バッファを管理するクラスを作成しました。
このクラスはこの次回以降も使用します。
ImageBufferクラス
package net.n2works.BitmapTest;
// SYSTEM PACKAGE
import android.graphics.Color;
/**
* 画像バッファクラス
*
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public class ImageBuffer
{
// 画像幅
private int mWidth;
public int GetWidth() { return mWidth; }
// 画像高さ
private int mHeight;
public int GetHeight() { return mHeight; }
// 画像バッファ
private int[] mBuffer;
public int[] GetBuffer() { return mBuffer.clone(); }
/**
* クラス定数
*/
private final int DEFAULT_W = 8;
private final int DEFAULT_H = 8;
/**
* コンストラクタ
*
* @param :
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public ImageBuffer()
{
mBuffer = null;
Resize(DEFAULT_W, DEFAULT_H);
}
/**
* コンストラクタ
* 継承用ダミーコンストラクタ
*
* @param : int[] : dummy : super(null)を継承クラスで実行
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public ImageBuffer(int[] dummy)
{
}
/**
* コンストラクタ
* 幅、高さ指定
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public ImageBuffer(int w, int h)
{
mBuffer = null;
Resize(w, h);
}
/**
* バッファのリサイズ
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public void Resize(int w, int h)
{
mWidth = w;
mHeight = h;
mBuffer = new int[w * h];
}
/**
* 指定座標pixelのindexを取得
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public int GetPixelAddress(int x, int y)
{
if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) {
return -1;
}
return GetPixelAddressNC(x, y);
}
/**
* 指定座標pixelのindexを取得
* ノンクリップ版
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public int GetPixelAddressNC(int x, int y)
{
return (mWidth * y + x);
}
/**
* 指定座標pixelにカラーを設定
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @param : int : color : カラー(ARGB)
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public void SetPixel(int x, int y, int color)
{
int pos = GetPixelAddress(x, y);
if (pos == -1) {
return;
}
mBuffer[pos] = color;
}
/**
* 指定座標pixelにカラーを設定
* ノンクリップ版
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @param : int : color : カラー(ARGB)
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public void SetPixelNC(int x, int y, int color)
{
mBuffer[GetPixelAddressNC(x, y)] = color;
}
/**
* 指定座標pixelのカラーを取得
* 失敗したら黒を返す
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return : int : カラー(ARGB)
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public int GetPixel(int x, int y)
{
int pos = GetPixelAddress(x, y);
if (pos == -1) {
return Color.BLACK;
}
return mBuffer[pos];
}
/**
* 指定座標pixelのカラーを取得
* ノンクリップ版
*
* @param : int : w : 画像幅
* @param : int : h : 画像高さ
* @return : int : カラー(ARGB)
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public int GetPixelNC(int x, int y)
{
return mBuffer[GetPixelAddressNC(x, y)];
}
/**
* バッファ間クリップ矩形転送
*
* @param : ImageBuffer : src : 転送元画像バッファ
* @param : ClipBltInfo : info : 転送情報
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public void BltClip(ImageBuffer src, ImageBuffer.ClipBltInfo info)
{
ImageBuffer.ClipInfo ss, ds;
ss = new ImageBuffer.ClipInfo(src.GetWidth(), src.GetHeight());
ds = new ImageBuffer.ClipInfo(GetWidth(), GetHeight());
// 転送領域なし
if (!CheckClipBlt(ss, ds, info)) {
return;
}
int[] src_buffer = src.GetBuffer();
for (int y = info.dy; y < info.dy + info.h; y++) {
int sx = src.GetPixelAddress(info.sx, info.sy + y - info.dy);
int dx = GetPixelAddress(info.dx, y);
System.arraycopy(src_buffer, sx, mBuffer, dx, info.w);
}
src_buffer = null;
}
/**
* 矩形転送時クリップ処理
*
* @param : ClipInfo : src : 転送元クリップ範囲情報
* @param : ClipInfo : dst : 転送先クリップ範囲情報
* @param : ClipBltInfo : info : 転送情報
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
private boolean CheckClipBlt(ImageBuffer.ClipInfo src, ImageBuffer.ClipInfo dst, ImageBuffer.ClipBltInfo info)
{
// 全くの領域外(スキップ可)
if ((info.w + info.dx) <= 0) {
return false;
}
if ((info.h + info.dy) <= 0) {
return false;
}
if (info.dx >= dst.w) {
return false;
}
if (info.dy >= dst.h) {
return false;
}
// 転送元の領域を参照しない(スキップ可)
if (info.sx >= src.w) {
return false;
}
if (info.sy >= src.h) {
return false;
}
if ((info.sx + info.w) < 0) {
return false;
}
if ((info.sy + info.h) < 0) {
return false;
}
// 元画像をはみだして参照してしまう(要クリッピング)
if ((info.sx + info.w) >= src.w) {
info.w = src.w - info.sx;
}
if ((info.sy + info.h) >= src.h) {
info.h = src.h - info.sy;
}
// 転送元の座標が負(要クリッピング)
if (info.sx < 0) {
info.dx += (-info.sx);
info.w -= (-info.sx);
info.sx = 0;
}
if (info.sy < 0) {
info.dy += (-info.sy);
info.h -= (-info.sy);
info.sy = 0;
}
// 転送先座標が負(要クリッピング)
if (info.dx < 0) {
info.sx -= info.dx;
info.w += info.dx;
info.dx = 0;
}
if (info.dy < 0) {
info.sy -= info.dy;
info.h += info.dy;
info.dy = 0;
}
// 転送元の矩形が、転送先の画像からはみ出る(要クリッピング)
if ((info.sx + info.w) > dst.w) {
info.w = dst.w - info.dx;
}
if ((info.sy + info.h) > dst.h) {
info.h = dst.h - info.dy;
}
// 転送領域がない(スキップ)
if (info.w < 1) {
return false;
}
if (info.h < 1) {
return false;
}
return true;
}
/**
* クリップ矩形塗りつぶし
*
* @param : ClipFillInfo : info : 塗りつぶし情報
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public void FillClip(ImageBuffer.ClipFillInfo info)
{
ImageBuffer.ClipInfo ds;
ds = new ImageBuffer.ClipInfo(GetWidth(), GetHeight());
// 転送領域なし
if (!CheckClipFill(ds, info)) {
return;
}
for (int y = info.dy; y < info.dy + info.h; y++) {
for (int x = info.dx; x < info.dx + info.w; x++) {
SetPixelNC(x, y, info.color);
}
}
}
/**
* 矩形塗りつぶしクリップ処理
*
* @param : ClipInfo : dst : 塗りつぶし先クリップ範囲
* @param : ClipFillInfo : info : 塗りつぶし情報
* @return :
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
private boolean CheckClipFill(ImageBuffer.ClipInfo dst, ImageBuffer.ClipFillInfo info)
{
// 描画の必要なし
if (info.dx >= dst.w) {
return false;
}
if (info.dy >= dst.h) {
return false;
}
if ((info.dx + info.w) <= 0) {
return false;
}
if ((info.dy + info.h) <= 0) {
return false;
}
// 描画先が負なら幅、高さを縮める
if (info.dx < 0) {
info.w += info.dx;
info.dx = 0;
}
if (info.dy < 0) {
info.h += info.dy;
info.dy = 0;
}
// 描画サイズがはみ出るので、同様に縮める
if ((info.dx + info.w) > dst.w) {
info.w = dst.w - info.dx;
}
if ((info.dy + info.h) > dst.h) {
info.h = dst.h - info.dy;
}
return true;
}
/**
* クリップ範囲情報
*
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
private class ClipInfo
{
public int w;
public int h;
public ClipInfo()
{
this.w = 0;
this.h = 0;
}
public ClipInfo(int w, int h)
{
this.w = w;
this.h = h;
}
}
/**
* 矩形転送クリップ情報
*
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public class ClipBltInfo
{
public int sx;
public int sy;
public int dx;
public int dy;
public int w;
public int h;
public ClipBltInfo()
{
this.sx = 0;
this.sy = 0;
this.dx = 0;
this.dy = 0;
this.w = 0;
this.h = 0;
}
public ClipBltInfo(int sx, int sy, int dx, int dy, int w, int h)
{
this.sx = sx;
this.sy = sy;
this.dx = dx;
this.dy = dy;
this.w = w;
this.h = h;
}
}
/**
* 矩形塗りつぶしクリップ情報
*
* @author : N.Nishimura
* @version : 1.0
* @since : 2011/02/22 1.0
*/
public class ClipFillInfo
{
public int dx;
public int dy;
public int w;
public int h;
public int color;
public ClipFillInfo()
{
this.dx = 0;
this.dy = 0;
this.w = 0;
this.h = 0;
this.color = Color.BLACK;
}
public ClipFillInfo(int dx, int dy, int w, int h, int color)
{
this.dx = dx;
this.dy = dy;
this.w = w;
this.h = h;
this.color = color;
}
}
}
バッファ間の矩形転送と矩形塗りつぶしの関数を実装しています。
矩形転送で使用しているSystem.arraycopyはC言語でいうところのmemcpyの代わりに使用しています。
javaはポインタがないのでこういう実装になりました。
以下サンプルです。
サンプル
package net.n2works.BitmapTest;
// SYSTEM PACKAGE
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff.Mode;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class BitmapTest extends Activity
{
// Bitmap
private Bitmap mBitmap;
private Bitmap mBitmap2;
/**
* Component
*/
// SurfaceView
private SurfaceView mView;
/**
* クラス定数
*/
private final int BG_COLOR = 0xff000000;
private final int BITMAP_W = 200;
private final int BITMAP_H = 200;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// BITMAPの作成
ImageBuffer buf = new ImageBuffer(BITMAP_W, BITMAP_H);
buf.FillClip(
buf.new ClipFillInfo(
0,
0,
BITMAP_W,
BITMAP_H,
0xffff0000
)
);
buf.FillClip(
buf.new ClipFillInfo(
BITMAP_W / 4,
BITMAP_H / 4,
BITMAP_W / 2,
BITMAP_H / 2,
0xff0000ff
)
);
mBitmap = Bitmap.createBitmap(BITMAP_W, BITMAP_H, Bitmap.Config.ARGB_8888);
mBitmap.setPixels(buf.GetBuffer(), 0, BITMAP_W, 0, 0, BITMAP_W, BITMAP_H);
ImageBuffer buf2 = new ImageBuffer(BITMAP_W / 2, BITMAP_H / 2);
buf2.BltClip(
buf,
buf2.new ClipBltInfo(
100,
100,
0,
0,
BITMAP_W / 2,
BITMAP_H / 2
)
);
mBitmap2 = Bitmap.createBitmap(BITMAP_W / 2, BITMAP_H / 2, Bitmap.Config.ARGB_8888);
mBitmap2.setPixels(buf2.GetBuffer(), 0, BITMAP_W / 2, 0, 0, BITMAP_W / 2, BITMAP_H / 2);
// Viewの設定
mView = new SurfaceViewEx(this);
setContentView(mView);
}
private class SurfaceViewEx extends SurfaceView
implements SurfaceHolder.Callback
{
/**
* Component
*/
private ScheduledExecutorService executor;
/**
* クラス定数
*/
private static final int FPS = 60;
public SurfaceViewEx(Context context)
{
super(context);
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
public void surfaceCreated(SurfaceHolder holder)
{
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(
new Runnable()
{
public void run()
{
Canvas c = getHolder().lockCanvas();
Draw(c);
getHolder().unlockCanvasAndPost(c);
}
},
1000 / FPS, 1000 / FPS,
TimeUnit.MILLISECONDS
);
}
public void surfaceDestroyed(SurfaceHolder holder)
{
mBitmap.recycle();
mBitmap2.recycle();
executor.shutdown();
}
protected void Draw(Canvas c)
{
// 背景色クリア
c.drawColor(BG_COLOR, Mode.SCREEN);
// Bitmap描画
c.drawBitmap(mBitmap, 0, 0, null);
c.drawBitmap(mBitmap2, 0.0f, 200.0f, null);
}
}
}