タブレット用プログラムの書き止め

android OS & iPadOS の記録。

基礎。Bitmap と Matrix で拡大縮小と回転を一度に処理をする。

2021-12-30 02:51:26 | Android studio 日記

検索で個々の処理説明はあるけど、連携の説明は無く Bitmap で拡大縮小、次に回転の2工程にしていた。
デベロッパーで Matrix にスケールの設定、角度の設定がある。
それを Bitmap の加工に使う。デベロッパーの説明はよく分からない。仕方ないので実験。

matrix.postScale( w倍率, h倍率 );
Bitmap加工
matrix.postRotate( 角度 );
Bitmap加工

分からないから、これで取り合えずやってみた。

90度回転はOK。
スクリーンサイズに合わせたはずが1/2に…。
スケールが2回処理された?。ということは。

matrix.postScale( w倍率, h倍率 );
matrix.postRotate( 角度 );
Bitmap加工

思い通りの結果が出た。一度で加工が完了。気持ち処理時間が短いかな?

 


今回の縛りは、スクリーン横長固定。画像は縦長。90度回転でスクリーンサイズにフィットさせる時。

    public void fitImage( ImageView view, Bitmap bm ) { // ポートレートBitmap only

        Bitmap tempBitmap = null;
        Matrix matrix = new Matrix();

        float mag = calcImageViewFit( bm.getHeight(), bm.getWidth() ); // 回転を踏まえ縦横サイズ入れ替えて倍率取得
        matrix.postScale( mag, mag );
        matrix.postRotate(270.00f );
        tempBitmap = Bitmap.createBitmap( bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);

        view.setImageBitmap( tempBitmap );
    }

    public float calcImageViewFit(int bw, int bh) {

        int screenWidth = getScreenWidth();   // スクリーンサイズ取得メソッド各自用意
        int screenHeight = getScreenHeight(); // スクリーンサイズ取得メソッド各自用意
        float fX = (float) screenWidth / (float) bw;
        float fY = (float) screenHeight / (float) bh;
        float ret = 1f;

        if (((bw > screenWidth) && (bh > screenHeight))
                || ((bw < screenWidth) && (bh < screenHeight))) {
            ret = Math.min(fX, fY);
        } else if ((bw > screenWidth) && (bh < screenHeight)) {
            ret = fX;
        } else if ((bw < screenWidth) && (bh > screenHeight)) {
            ret = fY;
        }
        return ret;
    }


スケールの倍率は、回転させたときに画像の横がスクリーンの縦になる事に注意。
縦横を対応させて倍率を取得する。

元画像サイズで表示させる場合は特に手を加える部分は無い。フィットや回転は加工前の準備で処理時間を短縮できる。
例えば、回転とフィット。元画像を回転してスクリーンに合わせるか。スクリーンに合わせてから回転か。
スクリーンより大きい画像は、スクリーンに合わせて縮小し、回転。
小さい画像は、回転してからスクリーンに合わせる。

細かく考えるのが面倒なら、Matrix で同時加工ですわ。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。エミュレーター上で画像ファイル表示切り替えテストの準備。copyfile()

2021-12-27 21:32:53 | Android studio 日記


手順は、
1、Assets フォルダーに画像ファイルを用意する。
2、Assets フォルダーの画像ファイル名のリストを作る。
3、転送先File情報を取得する。
4、転送する。

今まではアプリ固有フォルダに画像ファイルを転送して動作確認をしていた。
実機でUSB接続して、動作確認すると問題が起きる。自前のファイラーで移動できない。
なので、pictureフォルダへ画像ファイルをコピーする。これを最初からしておけば良かった。

クラス MyNumbersInString.java 前の記事参照。
エミュレーターのデバイス設定を変えるとデータは初期化されるようで消滅する。再度コピーが必要。

 


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

 

        // Pictures ディレクトリの File 情報の取得は、
        // File file = getExternalStoragePublicDirectory( DIRECTORY_PICTURES );
        // String  absolutePath = file.getAbsolutePath();
    }


    private AssetManager mAssetManager;
    private ArrayList< MyNumbersInString > mFileNameList = new ArrayList<>();

    private void setFileNameList() {


        String fName;
        String[] mList = new String[0];

        mAssetManager = getResources().getAssets();
        try {
            mList = mAssetManager.list("image");
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (String s : mList) {
            fName = s.toLowerCase(); //小文字に変換;
            if (fName.endsWith(".jpg") || fName.endsWith(".png")) { //画像ファイル選択
                mFileNameList.add(new MyNumbersInString(s));
            }
        }

        Collections.sort(
                mFileNameList, new Comparator< MyNumbersInString >() {
                    @Override
                    public int compare(MyNumbersInString mn1, MyNumbersInString mn2) {
                        return mn1.getName2().compareTo(mn2.getName2());
                    }
                }
        );

    }


    private void initFiles() {

        String src = null;
        File dst = null;

        File dir = new File( getExternalStoragePublicDirectory(DIRECTORY_PICTURES) , "image1" ); // "image1" > "image2" ディレクトリを増やせる


        if ( !dir.exists() && !dir.mkdirs() ) {
            // 失敗処理
            return;
        }

        for (int i = 0; i < mFileNameList.size(); i++ ) {
            src = mFileNameList.get( i ).getName();
            dst = new File( dir.getPath(), ( "001-" + src ) ); // "001-" > "002-"添付番号を変えて複数回実行するとファイル数を増やせる
            fileCopy( ( "image/" + src ), dst );
        }

    }

    public void fileCopy ( String src, File dst ){
        if ( dst.exists() ) return;

        try {
            InputStream is = getAssets().open( src );
            FileOutputStream fos = new FileOutputStream( dst, false );
            byte[] buffer = new byte[1024];
            int len = 0;
            while ( ( len = is.read( buffer ) ) >= 0 ) {
                fos.write( buffer, 0, len );
            }
            is.close();
            fos.close();
        } catch ( IOException e ) {
            return;
        }
    }

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。サムネイル画像を求める手法。BitmapFactory.Options と options.inSampleSize

2021-12-17 23:38:17 | Android studio 日記

RecyclerViewのアダプタ内でサムネイル画像を設定している。
自前で縮小サイズを算出してbitmapを縮小加工。setImageBitmap()で終了。
元サイズを読み出し、縮小加工、設定。メモリでのやり取りが多い。

デベロッパーの説明でサムネイルのサイズに近いところまで、2のべき乗で割って数値を求める。
BitmapFactory.Options.inSampleSizeに数値をセットしてデコード、システムが縮小出力してくれる。
近いサイズになっているので、そのままセットしてscaleTypeでフィット表示してもらう。
recyclerViewの表示が速くなった…。


RecyclerView用アダプタ内のプログラム

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        if ( imagePaths == null ) return ;// 画像ファイルパス配列の要素の有無

        Bitmap bitmap = loadImage( position );

        if (bitmap != null)
            holder.imageView.setImageBitmap( bitmap );
        else
            holder.imageView.setImageBitmap(  // デコードできないとき用意されてるリソースをセット
                  BitmapFactory.decodeResource( myContext.getResources(), R.drawable.not_find) );

        holder.textView.setText( imagePaths.get(position) );
        bitmap = null;
    }

    private Bitmap loadImage( int position ) {

        if ( position < 0 || position >= imagePaths.size() ) return null;

        String fName = imagePaths.get(position);
        if ( fName == null ) return null;

        File file = new File( fName );
        if ( !file.exists() || !file.isFile() ) return null;

        Bitmap scaleBitmap = null;
        try {
            BitmapFactory.Options op = new BitmapFactory.Options();
            op.inJustDecodeBounds = true; // 実データは読み込まない

            InputStream istream = new FileInputStream( file );
            BitmapFactory.decodeStream( istream, null, op );
            istream.close();

            // favoriteSizeは、サムネイルサイズ @dimen で宣言されているものを展開

            op.inSampleSize = calculateInSampleSize( op, favoriteSize, favoriteSize ); // 縮小サイズ
            op.inJustDecodeBounds = false; // 実データを読み込む

            istream = new FileInputStream( file );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return scaleBitmap;
    }

    public int calculateInSampleSize( BitmapFactory.Options op, int reqWidth, int reqHeight ) {
        int height = op.outHeight;
        int width = op.outWidth;
        int inSamplSize = 1;

        if ( height > reqHeight || width > reqWidth ) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;

            while ( ( halfHeight / inSamplSize ) >= reqHeight && ( halfWidth / inSamplSize ) >= reqWidth ) {
                inSamplSize *= 2; // 分母が2のべき乗
            }
        }
        return inSamplSize;
    }


デベロッパーは隅まで読むべし。理解できなくても(笑)

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。ビット操作。or 、and 、xor、not

2021-12-17 12:21:03 | Android studio 日記

画像ファイルのパス配列をソートして表示している、そのソート情報の管理。
パスと登録時刻の昇順、降順の選択を保存、パスか時刻かを選択する。その3個の変数が必要。
拡張を考えてビットスイッチと入出力を確立する。

int は32ビット。4ビットを単位とすると8個のスイッチとして使える。
[ 0000 ] 一番左をモードビットスイッチ、排他的にして必ず1ヶ所だけ(1)になるように管理。
今回は一番右のビットを昇順降順の状態をビットで保持。


    private int mSortMode; // 0b0000_0000_0000_0000_0000_0000_0000_0000


ビット操作のための対象ビットを定数化。

    private static final int BIT_SWITCH_NAME_ON = 0b1000_0000;
    private static final int BIT_SWITCH_NAME_MODE = 0b0001_0000;

    private static final int BIT_SWITCH_TIME_ON = 0b1000;
    private static final int BIT_SWITCH_TIME_MODE = 0b0001;

対象ビット左側、操作ビットを右側とすると
[or]
    0 | 0 => 0
    0 | 1 => 1
    1 | 0 => 1
    1 | 1 => 1
操作ビットに(1)を設定すれば、ビットをONできる。対象=対象|操作;or 対象|=操作;

[and]
    0 & 0 => 0
    0 & 1 => 0
    1 & 0 => 0
    1 & 1 => 1
両方(1)のときに出力される。ビットが立っているか検査に使う。(対象&操作)!=0

[xor]
    0 ^ 0 => 0
    0 ^ 1 => 1
    1 ^ 0 => 1
    1 ^ 1 => 0
両方が同じ状態のときに(0)となる。操作(1)で(対象^操作)==0、ON状態。
操作(0)なら、(対象^操作)==0、OFF状態。私の検査は&常用か。

[not]
    ~0 => 1
    ~1 => 0

対象を0にする準備に使う。

対象ビットだけを0にするには&を使う。
操作側のビットは、対象ビットの位置を0に、対象ビット位置以外は1にする事で、&の後に変化するのは対象ビット位置のみになる。

 操作 0001
~操作 1110

 対象 1111
&操作 1110
      1110
操作したいビットだけをOFFにできる。

ビット単位のON、OFF、検査が確立できた。
定数とメソッドで名前、時刻、モード、情報の保存、入出力が可能。

 

    private static final int BIT_SWITCH_NAME_ON = 0b1000_0000;
    private static final int BIT_SWITCH_NAME_MODE = 0b0001_0000;

    private static final int BIT_SWITCH_TIME_ON = 0b1000;
    private static final int BIT_SWITCH_TIME_MODE = 0b0001;

    private int mSortMode = BIT_SWITCH_TIME_ON | BIT_SWITCH_TIME_MODE;
    // 0b0000_0000_0000_0000_0000_0000_0000_1001

    private boolean isNameOn() {
        return ( mSortMode & BIT_SWITCH_NAME_ON ) != 0; // name ON
  }
    private boolean isNameUpSort() {
        return ( mSortMode & BIT_SWITCH_NAME_MODE ) == 0; // name UP mode
    }

    private boolean isTimeOn() {
        return ( mSortMode & BIT_SWITCH_TIME_ON ) != 0; // time ON
    }
    private boolean isTimeUpSort() {
        return ( mSortMode & BIT_SWITCH_TIME_MODE ) == 0; // time UP mode
    }

    private void setName( boolean sw ) {
        if (sw) mSortMode |= BIT_SWITCH_NAME_ON; // sw bit on
        else mSortMode &=  ~BIT_SWITCH_NAME_ON; // sw bit off
    }
    private void setTime( boolean sw ) {
        if (sw) mSortMode |= BIT_SWITCH_TIME_ON;
        else mSortMode &= ~BIT_SWITCH_TIME_ON;
    }
    private void changeSortMode() {
        if ( isNameOn() ) {
            if ( isNameUpSort() )
                mSortMode &= ~BIT_SWITCH_NAME_MODE;
            else
                mSortMode |= BIT_SWITCH_NAME_MODE;
        }
        if ( isTimeOn() ) {
            if ( isTimeUpSort() ) // 1001
                mSortMode &= ~BIT_SWITCH_TIME_MODE; // 1001 => 1000
            else
                mSortMode |= BIT_SWITCH_NAME_MODE; // 1000 => 1001
        }
    }


setName( boolean sw )
setTime( boolean sw )
本来、このメソッド内で排他的操作を実行させる。
Name が ON なら time を OFF。
time が ON なら Name を OFF。

 

mSortMode &= 0b0111_0111_0111_0111_0111_0111_0111_0111; // sw bit clear
mSortMode |= BIT_SWITCH_TIME_ON; // sw bit on

とか。

 

次の懸案はスレッド…。よく分からない。
意図したとおりに動かない。
スレッドからユーザーインターフェースにアクセスしてはいけない理由は分かった。
view.post()とコールバックを組み合わせるのをデベロッパーで知った。
パターンを考えて試してみる。やっぱり、グルグルは必要だわ。

あと、サムネイル排出メソッドをメモリ低消費に改良する。時短にも結び付くから。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。ArrayList<>のsort()など。昇順、降順。

2021-12-16 14:00:29 | Android studio 日記

プリファレンス保存、読み出しでSet<> <=> List<> の変換だけで楽だった。
しかし、新しく登録したものが最初に出てこなくなる副作用が…。これは気に入らない。
変換時に順番が入れ替わってしまうようだ。
なので、昇順降順の切り替え、名前、登録時の時間での並び替えを実現する配列を考える。

並び替えの元を作る。パス、登録時の時間、プリファレンス用文字列。
追加、削除、プリファレンス入出力に都合のいい3種のデータをパックにした。

public class MyStringForFavorite {

    private String main = ""; // 画像パス
    private String time = ""; // 登録時の時間
    private String join = ""; // 結合文字列

    public MyStringForFavorite( String name ) { // name は画像パスか、時間+",,"+パスの限定。それ以外は飛ぶ・・かな。
        join = name;
        String[] mSplit = name.split(",,"); // 時間+",,"+パスを分離。

        if (mSplit.length == 1){ // 時間成分が無い
            main = mSplit[0];
            time = getNowDate();
            join = time + ",," + main; // 時間+",,"+パスで結合保存。
        } else {
            time = mSplit[0];
            main = mSplit[1];
        }
    }
    public String getPath(){ return main; }
    public String getTime(){ return time; }
    public String getJoin(){ return join; }
    private String getNowDate() {
        @SuppressLint("SimpleDateFormat") DateFormat df = new SimpleDateFormat( "yyyy-MM-dd--HH-mm-ss" );
        Date date = new Date( System.currentTimeMillis() );
        return df.format(date);
    }
}

 

// 上の MyString クラスを配列で使うための MyList クラスを作る。


SetToArrayList(Set<>) でプリファレンスから読み込んだ Set<> を ArrayList<> まで変換させている。

ソート部分の
Collections.sort(
    mPathNameList, new Comparator< MyStringForFavorite >() {
        @Override
        public int compare(MyStringForFavorite mn1, MyStringForFavorite mn2) {
            return - mn1.getTime().compareTo(mn2.getTime());
        }
    }
);
ここで時間を元に並び変え。return値にマイナス符号を付加して降順配列としている。
マイナス符号を付けなければ、この場合は昇順配列となる。
追加、削除、その他ソート、RecyclerView用のList<>排出、リファレンス用のSet<>排出を用意。

public class MyFavoriteList {

    private ArrayList< MyStringForFavorite > mPathNameList;

    MyFavoriteList() {
          mPathNameList = new ArrayList< MyStringForFavorite >();
    }

    public void SetToArrayList( Set< String > sou ) { // リファレンスデータ変換

        if ( sou == null || sou.isEmpty() ) {
            clear();
            return;
        }
        List< String > temp = new ArrayList< String >(sou); // Set<> を List<>に変換。

        for (int i=0; i < temp.size(); i++ ) {
            mPathNameList.add( new MyStringForFavorite(temp.get(i))); // ArrayListへ追加。
        }

        Collections.sort(
                mPathNameList, new Comparator< MyStringForFavorite >() {
                    @Override
                    public int compare(MyStringForFavorite mn1, MyStringForFavorite mn2) {
                        return - mn1.getTime().compareTo(mn2.getTime());
                    }
                });
    }

    public void add( String path ) {
        if ( path == null ) return;
        mPathNameList.add( 0, new MyStringForFavorite( path )); // 引数 0 は先頭追加
    }
    public boolean remove(int index) {
        if ( mPathNameList.isEmpty() || index < 0 || index >= mPathNameList.size() ) return false;
        try {
            mPathNameList.remove(index);
        } catch (UnsupportedOperationException e ) {
            e.printStackTrace();
        }
        return true;
    }
    public void sortTime(boolean flag) {

        if ( flag ) {
            Collections.sort(
                    mPathNameList, new Comparator< MyStringForFavorite >() {
                        @Override
                        public int compare(MyStringForFavorite mn1, MyStringForFavorite mn2) {
                            return mn1.getTime().compareTo(mn2.getTime());
                        }
                    } );
        } else {
//省略 return - mn1.getTime().compareTo(mn2.getTime()); マイナス符号付くだけ
       }
    }
    public void sortPath(boolean flag) {

        if ( flag ) {
            Collections.sort(
                    mPathNameList, new Comparator< MyStringForFavorite >() {
                        @Override
                        public int compare(MyStringForFavorite mn1, MyStringForFavorite mn2) {
                            return mn1.getPath().compareTo(mn2.getPath());
                        }
                    } );
        } else {
//省略 return - mn1.getPath().compareTo(mn2.getPath()); マイナス符号付くだけ
        }
    }

    public List< String > getList() { // RecyclerView用List<>データ
        List< String > list = new ArrayList< String >();

        for (int i=0; i < mPathNameList.size(); i++ ){
            list.add( mPathNameList.get(i).getPath() );// フルパス
        }
        return list;
    }
    public Set< String > getSet() { // リファレンス用Set<>データ
        List< String > list = new ArrayList< String >();

        for (int i=0; i < mPathNameList.size(); i++ ){
            list.add( mPathNameList.get(i).getJoin() );// 日時+フルパス
        }
        return new HashSet< String >( list );
    }

    public String getPath(int index) {
        if ( mPathNameList.isEmpty() || index < 0 || index >= mPathNameList.size() ) return null;
        return mPathNameList.get(index).getPath();
    }
    public boolean isEmpty() { return mPathNameList.isEmpty(); }
    public int size() { return mPathNameList.size(); }

    public void clear() { mPathNameList.clear(); }
    public void finish() {
        mPathNameList.clear();
        mPathNameList = null;
    }
}

なんか、
Set< String > listData = new HashSet< String >( list );
法則性の処理があるみたいで、削除、先頭追加を繰り返してみても同じような場所に配置換えされた。
まぁ、しょうがない。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする