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

android OS & iPadOS の記録。

基礎。サムネイル画像の作成と保存。

2022-07-11 15:05:49 | Android studio 日記

ソース画像の絶対パスからサムネイル画像を作り、出力用絶対パスに保存するクラスを切り出す。

サムネイル画像の保存サイズは、正方形のサイズを指定。
元画像の縦横比をそのままスケールダウンし、指定サイズに収める。

出力のファイル拡張子(JPG/PNG)から書き出しファイル形式を決める。
入力(source.png)、出力(destination.jpg)とした時に

toThumbnailFile( new File("source.png"), new File("destination.jpg"), 100 )
入力は"PNG"だけど作られたものは"JPG"になる。


使用例:クラスをインポートした上で

File souFile = new File( 入力絶対パス );
File destFile = new File( 出力絶対パス );

boolean ret = new MyMakeThumbnail.toThumbnailFile( souFile, destFile, 100 )

元画像があれば、サムネイル画像が作成、または上書きされる。

 


前省略
public class MyMakeThumbnail {
    int saveSize;

    public boolean toThumbnailFile( File souFile, File destFile, int saveSize ) {
        if (souFile == null || !souFile.exists() || !souFile.isFile()) return false;

        this.saveSize = saveSize;
        Bitmap bitmap = thumbnaileImage( souFile );
        if ( bitmap != null ) {
            try {
                FileOutputStream fos = new FileOutputStream( destFile );
                Bitmap.CompressFormat bcf = getCompressFormat( destFile.getName() );
                if ( bcf != null ) bitmap.compress( bcf, 85, fos );
                fos.close();
            } catch (IOException e) {
                return false;
            }
        }
        return true;
    }

    private Bitmap thumbnaileImage( File souFile ) {
        Bitmap scaleBitmap = null;
        try {
            BitmapFactory.Options op = new BitmapFactory.Options();
            op.inJustDecodeBounds = true;
            InputStream istream = new FileInputStream( souFile );
            BitmapFactory.decodeStream( istream, null, op );
            op.inSampleSize = calculateInSampleSize( op );
            op.inJustDecodeBounds = false;
            istream.close();

            istream = new FileInputStream( souFile );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
            if ( scaleBitmap == null ) return null;

            Matrix matrix = new Matrix();
            float mag = calcMag( scaleBitmap.getWidth(), scaleBitmap.getHeight() );
            matrix.postScale( mag, mag );
            scaleBitmap = Bitmap.createBitmap( scaleBitmap, 0, 0, scaleBitmap.getWidth(), scaleBitmap.getHeight(), matrix, true);
        } catch (OutOfMemoryError | IOException ignored) {
            return null;
        }
        return scaleBitmap;
    }

    private int calculateInSampleSize(BitmapFactory.Options op) {
        int height = op.outHeight;
        int width = op.outWidth;
        int inSamplSize = 1;

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

            while ( ( halfHeight / inSamplSize ) >= saveSize &&
                    ( halfWidth / inSamplSize ) >= saveSize
            ) {
                inSamplSize *= 2;
            }
        }
        return inSamplSize;
    }

    private Bitmap.CompressFormat getCompressFormat(String name ) {
        String t = name.toLowerCase();
        if ( t.endsWith( ".jpg" ) ) return Bitmap.CompressFormat.JPEG;
        if ( t.endsWith( ".png" ) ) return Bitmap.CompressFormat.PNG;
        return null;
    }

    private float calcMag( int souX, int souY ) {
        float magW = (float)saveSize / (float)souX;
        float magH = (float)saveSize / (float)souY;

        return Math.min(magW, magH);
    }
}

 

MyAsyncTaskは次のように短くなった。MyMakeThumbnailも使いまわしできる。

public class MyAsyncTask extends AsyncTask<String,Object,Object> {

    private MyMainData mMainData;
    @SuppressLint("StaticFieldLeak")
    private RecyclerView mRecyclerView;
    private File mCacheDir;
    private final int vID;

    public MyAsyncTask( MyMainData data, RecyclerView rv, int id ) {
        super();
        mMainData = data;
        mCacheDir = data.getCacheDir();
        mRecyclerView = rv ;
        vID = id;
    }

    @Override
    protected Object doInBackground(String... p) {
        File souFile = new File(p[0]);
        if ( !souFile.exists() || !souFile.isFile() ) return false;
        if ( mCacheDir == null || ( !mCacheDir.exists() && !mCacheDir.mkdirs() ) ) return false;

        File cacheFile = new File( mCacheDir, souFile.getName() );
        if ( cacheFile.exists() ) return false;

        return new MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, 128);
    }

    @Override
    protected void onPostExecute( Object result ) {
        if ( (boolean)result ) {
            // RecyclerViewアダプタは不定期更新される。別スレッド稼働時は必ずチェックする。
            // 同じRecyclerViewアダプタのオブジェクトならnotifyItemChanged(vID)で更新。
            if (Objects.equals(mRecyclerView.getAdapter(), mMainData.getThumbnailPage().getRecyclerView().getAdapter()))
                    Objects.requireNonNull(mRecyclerView.getAdapter()).notifyItemChanged(vID);

        }

        finish();
    }
    private void finish() {
        mCacheDir = null;
        mRecyclerView = null;
        mMainData = null;
    }
}