Android SDK/NDKでZIP書庫内の画像ファイルを順々に表示する

test123_01.png
今回はこれまでに実装してきたAndroid NDKでZIP書庫内の画像ファイルを解凍するAndroid SDKでJPG画像を画像に合わせて拡大縮小表示するを組み合わせて、ZIP書庫内の画像を順々に表示させるアプリを作成する。

まずは雛形となるプロジェクトをEasyProjectGenerator for Androidで作る。C++、HelloJNI、STL有効とする。

test123_02.png Eclipseでプロジェクトを読み込み、まずはZIP書庫解凍用のライブラリlibunzipを使うための設定をAndroid.mkに追加する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test123
LOCAL_SRC_FILES := Test123.cpp
LOCAL_LDLIBS    := -lz -lunzip

LOCAL_IS_SUPPORT_LOG := true
ifeq ($(LOCAL_IS_SUPPORT_LOG),true)
	LOCAL_LDLIBS += -llog
endif

include $(BUILD_SHARED_LIBRARY)

test123_03.png 次にAndroid NDKでZIP書庫内の画像ファイルを解凍するで作成したZipedImages.hと同じものを追加する。
#ifndef	_ZIPEDIMAGES_H
#define	_ZIPEDIMAGES_H

#include<string>
#include<vector>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <android/log.h>


#include "unzip.h"

#define	MAX_PATH	256

#define	bool	unsigned int
#define	true	(1)
#define	false	(0)




class	CZipedImages
{
protected:

	unzFile		_zipFile;
	std::vector<unz_file_pos>	_vFilePos;


public:

	CZipedImages()
	{
		_zipFile = 0;
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::CZipedImages()");
	}

	~CZipedImages()
	{
		Close();
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::~CZipedImages()");
	}

	bool	IsOpen(void)
	{
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::IsOpen()");
		return	(_zipFile != 0) ? true : false;
	}


	bool	OpenZipFile(const char* pszFile)
	{
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::OpenZipFile()");
		if(IsOpen() == true)
			return	false;

		int		nRet;

		_zipFile = unzOpen(pszFile);

		nRet = unzGoToFirstFile(_zipFile);
		while(nRet == UNZ_OK)
		{
			char	pszFileName[MAX_PATH];
			unz_file_info	info;
			unz_file_pos	pos;
			std::string		strFile;
			std::string		strExt;

			unzGetCurrentFileInfo(_zipFile,&info,pszFileName,MAX_PATH,NULL,0,NULL,0);
			strFile	= pszFileName;
			strExt	= strFile.substr(strFile.length() - 4,4);		//いい加減な方法で拡張子取得

			if(strExt == ".JPG" || strExt == ".jpg" || strExt == ".PNG" || strExt == ".png")				//いい加減な方法で拡張子チェック
			{
				unzGetFilePos(_zipFile,&pos);
				_vFilePos.push_back(pos);
			}
			else
			{
				//処理されないファイル/フォルダをログ出力
				__android_log_print(ANDROID_LOG_INFO,TAG,"NOT process : (%s)",pszFileName);
			}

			nRet = unzGoToNextFile(_zipFile);
		}

		return	true;
	}


	void	Close(void)
	{
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Close()");

		if(_zipFile != 0)
			unzClose(_zipFile);
		_zipFile = 0;
		_vFilePos.clear();
	}



	int		GetCount(void)
	{
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::GetCount()");

		return	_vFilePos.size();
	}


	bool	Extract(int nIndex,const char* pszFolder,std::string* pstrFile)
	{
		__android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract()");

		if(IsOpen() == false)
			return	false;

		if(nIndex > _vFilePos.size() || nIndex < 0)
			return	false;

		int		nRet;
		char	pszFileName[MAX_PATH];
		unz_file_info	info;

		nRet = unzGoToFilePos(_zipFile,&_vFilePos[nIndex]);
		if(nRet != UNZ_OK)
			return	false;

		nRet = unzGetCurrentFileInfo(_zipFile,&info,pszFileName,MAX_PATH,NULL,0,NULL,0);
		if(nRet != UNZ_OK)
			return	false;
		__android_log_print(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract() in process %s",pszFileName);


		std::string	strPath;

		//保存先ファイルパス作成
		{
			strPath = pszFolder;
			if(strPath.substr(strPath.length() - 1,1) != "/")
				strPath += "/";

			if(pstrFile != NULL && *pstrFile != "")
			{
				if((*pstrFile)[0] != '/')
					strPath += *pstrFile;
				else
					strPath += pstrFile->substr(1,pstrFile->length() - 1);
			}
			else
			{
				char	pszBuff[256];
				std::string	strExt;

				strExt	= pszFileName;
				strExt	= strExt.substr(strExt.length() - 4,4).c_str();	//いい加減な方法で拡張子取得
				sprintf(pszBuff,"%d%s",nIndex,strExt.c_str());

				if(pstrFile)
					*pstrFile = pszBuff;
				strPath.append(pszBuff);
			}
		}
		__android_log_print(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract() : save to %s",strPath.c_str());

		//解凍
		{
			FILE	*fp;

			fp = fopen(strPath.c_str(),"w");
			if(fp == NULL)
				return	false;

			char	szBuffer[32768];
			int		dwSizeRead;

			nRet = unzOpenCurrentFile(_zipFile);
			if(nRet == UNZ_OK)
			{
				while ((dwSizeRead = unzReadCurrentFile(_zipFile,szBuffer,sizeof(szBuffer))) > 0)
				{
					fwrite(szBuffer,1,dwSizeRead,fp);
				}
				unzCloseCurrentFile(_zipFile);
			}
			fclose(fp);
		}

		return	true;
	}


};

#endif

test123_04.png
Javaからネイティブで実装したZIP書庫操作用の関数にアクセスできるようにJNIを実装する。CZipedImagesのメンバー関数を単純にJNIでラッピングした。これでネイティブ側の実装は終りだ。
#if(true)
#define LOCAL_LOG
#define LOCAL_LOGD
#endif

#include <string.h>
#include <jni.h>

#ifdef LOCAL_LOG
#include <android/log.h>
#endif

#define	TAG	"Test123"
#include "ZipedImages.h"

CZipedImages	g_cZip;


extern "C"
jint Java_com_Test123_ZipImageFileView_OpenZipFile(JNIEnv* env,jobject thiz,jstring jstrFile)
{
	bool	ret;

	const	char*	pszFile = env->GetStringUTFChars(jstrFile,NULL);
	ret = g_cZip.OpenZipFile(pszFile);
	env->ReleaseStringUTFChars(jstrFile,pszFile);

	return	(ret == true) ? 1 : 0;
}

extern "C"
jint Java_com_Test123_ZipImageFileView_GetInZipCount(JNIEnv* env,jobject thiz)
{
	return	g_cZip.GetCount();
}


extern "C"
jstring Java_com_Test123_ZipImageFileView_Extract(JNIEnv* env,jobject thiz,jint jnIndex,jstring jstrFolder)
{
	std::string		strFile;

	int		nIndex = jnIndex;
	bool	ret;

	const	char*	pszFolder = env->GetStringUTFChars(jstrFolder,NULL);
	ret = g_cZip.Extract(nIndex,pszFolder,&strFile);
	env->ReleaseStringUTFChars(jstrFolder,pszFolder);

	if(ret)
		return env->NewStringUTF(strFile.c_str());
	else
		return env->NewStringUTF("");
}


extern "C"
void Java_com_Test123_ZipImageFileView_Close(JNIEnv* env,jobject thiz)
{
	g_cZip.Close();
}

test123_05.png
次にImageFileView.javaファイルを追加して、画像ファイルを読み込みビュー表示するクラスを実装する。

基本的にAndroid SDKでJPG画像を画像に合わせて拡大縮小表示すると同じだが、画像ファイルが大きくなるとメモリが足りなくなって落ちる問題の修正などをしている。nScaleの計算が間違えているのはご愛嬌。
package com.Test123;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;


public class ImageFileView extends View
{
	protected	Bitmap	_bmpImageFile;		//画像ファイル
	protected	Bitmap	_bmpResized;		//リサイズ画像

	protected	int		_nWidthDisp;
	protected	int		_nHeightDisp;
	
	public ImageFileView(Context context)
	{
		super(context);

		//画面サイズ取得
		WindowManager	wmWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display			display = wmWindowManager.getDefaultDisplay();
		_nWidthDisp		= display.getWidth();
		_nHeightDisp	= display.getHeight();
	}

	//設定の保存
	public void OnSaveState(Bundle outState)
	{
		if(outState == null)
			return;
	}

	//設定の読込
	public void OnLoadState(Bundle savedInstanceState)
	{
		if(savedInstanceState == null)
			return;
	}
	


	//画像を大まかに縮小して_bmpImageFileへ読み込み、
	//表示用にリサイズして_bmpResizedへ保存する
	public boolean	LoadImageFile(String strFile,boolean bInvalidate)
	{
		//画像の解析(大きい画像が開けない対策)
		BitmapFactory.Options	options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		_bmpImageFile = BitmapFactory.decodeFile(strFile,options);
		
		int		nWidth = options.outWidth;
		int		nHeight = options.outHeight;


		int		nScale;

		//nScaleは2の乗数にする
		{
			int	nScaleX = (int)(float)(nWidth / _nWidthDisp + 0.5f);
			int	nScaleY = (int)(float)(nHeight / _nHeightDisp + 0.5f);

			nScale = (nScaleX < nScaleY) ? nScaleX : nScaleY;
			
			int		i = 1;

			while(nScale > i)
			{
				i *= 2;
			}
			nScale = i - 1;
			if(nScale <= 0)
				nScale = 1;
		}
		
		//画像の本読み込み
		options.inJustDecodeBounds = false;
		options.inSampleSize = nScale;
		_bmpImageFile = BitmapFactory.decodeFile(strFile,options);


		/////////////////////////////////
		//画像のリサイズ
		
		nWidth = _bmpImageFile.getWidth();
		nHeight = _bmpImageFile.getHeight();

		float	fScale;

		//拡大縮小率取得
		if(true)
		{
			//画像に合わせる
			if((long)nWidth * _nHeightDisp > (long)_nWidthDisp * nHeight)
				fScale = (float)_nWidthDisp / nWidth;
			else
				fScale = (float)_nHeightDisp / nHeight;
		}
		else if(false)
		{
			//高さに合わせる
			fScale = (float)_nHeightDisp / nHeight;
		}
		else
		{
			//幅に合わせる
			fScale = (float)_nWidthDisp / nWidth;
		}

		
		//リサイズ
		Matrix	matrix = new Matrix();
		matrix.postScale(fScale,fScale,0,0);
		_bmpResized = Bitmap.createBitmap(_bmpImageFile,0,0,nWidth,nHeight,matrix,true);

		if(bInvalidate)
			invalidate();		//表示更新

		return	(_bmpImageFile != null) ? true : false;
	}


	@Override
	protected void onDraw(Canvas canvas)
	{
		if(_bmpImageFile == null)
		{
			super.onDraw(canvas);
			return;
		}

		
		//描画
        Rect	rect = new Rect(0,0,_bmpResized.getWidth(),_bmpResized.getHeight());
        canvas.drawBitmap(_bmpResized,rect,rect,null);
	}

}


test123_06.png
次にImageFileView派生でZipImageFileViewクラスを作る。

ZIPファイルから画像ファイルを解凍、ImageFileViewに読み込ませ、画像ファイルを削除という単純な流れにした。
package com.Test123;

import java.io.File;

import android.content.Context;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.MotionEvent;


public class ZipImageFileView extends ImageFileView
{
	private	int		_nImageIndex = -1;	//現在読み込まれている画像のzip内の画像インデックス
	private	int		_nImageCount;		//zip内の画像数
	
	public ZipImageFileView(Context context)
	{
		super(context);
		
		//zipファイル読み込み
		OpenZipFile("/sdcard/desire_img.zip");
		_nImageCount = GetInZipCount();
	}

	//設定の保存
	@Override
	public void OnSaveState(Bundle outState)
	{
		super.OnSaveState(outState);
		if(outState == null)
			return;

		outState.putInt("_nImageIndex",_nImageIndex);
	}

	
	//設定の読込
	@Override
	public void OnLoadState(Bundle savedInstanceState)
	{
		super.OnLoadState(savedInstanceState);
		if(savedInstanceState == null)
			return;

		int		nIndex;
		
		nIndex = savedInstanceState.getInt("_nImageIndex");
		LoadImageFromZip(nIndex,true);
	}

	
	
	public boolean	LoadImageFromZip(int nIndex,boolean bInvalidate)
	{
		if(nIndex >= _nImageCount || _nImageCount == 0 || nIndex < 0)
			return	false;
		if(nIndex == _nImageIndex)
			return	true;				//すでに読み込まれている

		String	strFile;

		//画像解凍
		strFile = Extract(nIndex,"/sdcard/");
		if(strFile == "")
			return	false;
		strFile = "/sdcard/" + strFile;

		//画像読み込み
		LoadImageFile(strFile,bInvalidate);
		_nImageIndex = nIndex;

		//ファイル削除
		File	file = new File(strFile);
		file.delete();

		return	true;
	}

	
	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		//タッチイベント取得
		if(event.getAction() == MotionEvent.ACTION_DOWN)
		{
			int		nIndex;

			nIndex = _nImageIndex + 1;
			if(nIndex >= _nImageCount)
				nIndex = 0;

			LoadImageFromZip(nIndex,true);
		}
		return true;
	}	

	
	@Override
	protected void onDraw(Canvas canvas)
	{
		if(_bmpImageFile == null)
		{
			LoadImageFromZip(0,true);
			return;
		}

		super.onDraw(canvas);
	}
	
	

	private native int  OpenZipFile(String strFile);
	private native int  GetInZipCount();
	private native String  Extract(int nIndex,String strFolder);
	private native void  Close();

	static
	{
		System.loadLibrary("Test123");
	}
}

test123_07.png 最後にアクティビティに作成したビューを割り当てる。
package com.Test123;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class Test123Act extends Activity
{
	private	ImageFileView	_view;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		_view = new ZipImageFileView(this);
		setContentView(_view);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);

		if(_view != null)
			_view.OnSaveState(outState);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState)
	{
		super.onRestoreInstanceState(savedInstanceState);
		
		if(_view != null)
			_view.OnLoadState(savedInstanceState);
	}
}

test123_08.png
これで実行するとZIP書庫内の画像ファイルが表示され、画面をタッチすると、、、

test123_09.png
書庫内の次の画像が表示された。

先読み処理や別スレッドでの解凍処理などをしていないため、全体的にモッサリ。実機ではまぁまぁ使えるものの、VirtualPC上のAndroidエミュレーターでは論外なほどの遅さでした。




ファイルをダウンロード


カテゴリー「android」 のエントリー