Androidアプリ開発

画像データの向きに対応したファイルコピー

この記事は約18分で読めます。
スポンサーリンク

こんにちは、まっさん(@Tera_Msaki)です。

この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaでの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)

この記事のテーマ


画像データの向きを考慮して、ファイルコピーする

デジタルカメラで撮影した画像ファイルには、
Exif という画像ファイルに関するメタデータが含まれています。
Andoroid アプリ開発で、
画像ファイルから Bitmap を生成して、ImageView にセットした場合、
画像の向きが正しく表示されない ことがあります。
画像の向きを正しく表示するためには、
画像ファイルから
Exif を取得して、
正しい向きに変換する必要があります。

Exif

◎ポイント
Exif(Exchangeable image file format)は、
日本電子工業振興協会 (
JEIDA )で規格化された
写真用のメタデータを含む画像ファイルフォーマットです。
カメラの機種や撮影時の条件情報を画像に埋め込んで、
ビューワやフォトレタッチなどで応用します。
画像データの向きに対応したファイルコピーでは、
画像ファイルから、
Exif を読み出し、
画像データを向きに応じて、
正しい向きに回転させて、ファイルコピーします。

◎依存関係の宣言
モジュールの build.gradle ファイルに依存関係を追加します。

:
dependencies {
    implementation 'androidx.exifinterface:exifinterface:1.3.5'
    :
}

※パージョン1.3.5 は、2022年11月時点のものです

◎画像ファイルからExif情報を取得して、正しい向きに回転する
画像ファイルから画像データ(Bitmap)と Exif を取得します。
Matrix を使用して、Exif の orientation(方向)で Bitmap を変換(回転)します。

    public void binaryFileCopy(Uri inFileUri, String outFile, int type) {
        try (InputStream inputStream = context.getContentResolver().openInputStream(inFileUri)) {
            ExifInterface exifInterface = new ExifInterface(inputStream);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(context.getContentResolver().openFileDescriptor(inFileUri, "r").getFileDescriptor());
            Bitmap bitmap2;
            float resizeScale;
            float width = Math.min(bitmap.getWidth(), 1920);
            Matrix matrix = new Matrix();
            matrix.reset();
            switch(orientation) {
                case 2:  // ORIENTATION_FLIP_HORIZONTAL 左右反転
                    resizeScale = width / bitmap.getWidth();
                    matrix.postScale(resizeScale, -resizeScale);
                    matrix.postTranslate(0, bitmap.getHeight() * resizeScale);
                    break;
                case 3:  // ORIENTATION_ROTATE_180 180度回転
                    resizeScale = width / bitmap.getWidth();
                    matrix.postRotate(180, bitmap.getWidth() / 2f, bitmap.getHeight() / 2f);
                    matrix.postScale(resizeScale, resizeScale);
                    break;
                case 4:  // ORIENTATION_FLIP_VERTICAL 上下反転
                    resizeScale = width / bitmap.getWidth();
                    matrix.setScale(-resizeScale, resizeScale);
                    matrix.postTranslate(bitmap.getWidth() * resizeScale,0);
                    break;
                case 5:  // ORIENTATION_TRANSPOSE 270度回転+上下反転
                    resizeScale = width / bitmap.getHeight();
                    matrix.postRotate(270, 0, 0);
                    matrix.postScale(resizeScale, -resizeScale);
                    break;
                case 6:  // ORIENTATION_ROTATE_90 90度回転
                    resizeScale = width / bitmap.getHeight();
                    matrix.postRotate(90, 0, 0);
                    matrix.postScale(resizeScale, resizeScale);
                    matrix.postTranslate(bitmap.getHeight() * resizeScale, 0);
                    break;
                case 7:  // ORIENTATION_TRANSVERSE 90度回転+90度反転
                    resizeScale = width / bitmap.getHeight();
                    matrix.postRotate(90, 0, 0);
                    matrix.postScale(resizeScale, -resizeScale);
                    matrix.postTranslate(bitmap.getHeight() * resizeScale, bitmap.getWidth() * resizeScale);
                    break;
                case 8:  // ORIENTATION_ROTATE_270 270度回転
                    resizeScale = width / bitmap.getHeight();
                    matrix.postRotate(270, 0, 0);
                    matrix.postScale(resizeScale, resizeScale);
                    matrix.postTranslate(0, bitmap.getWidth() * resizeScale);
                    break;
                default: // ORIENTATION_NORMAL 変更不要
                    resizeScale = width / bitmap.getWidth();
                    matrix.preScale(resizeScale, resizeScale);
            }
            bitmap2 = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(),bitmap.getHeight(), matrix, true);
            setFile(outFile, type);
            writeFileBitmap(bitmap2);
        } catch (IOException e) {
            Log.d(TAG, Objects.requireNonNull(e.getMessage()));
            e.printStackTrace();
        }
    }

ファイルコピー関数では、
Intent 使用して指定した画像ファイルを Uri、
出力ファイルは、ファイル名と出力先区分を引数としています。
①画像ファイルの Exif を取得して、Bitmap に展開
②展開した Bitmap を Matrix を使用して正しき向きに変換して、ファイル出力
※画像ファイルのサイズが大きいため、HDサイズに縮小しています

◎Bitmapをファイル出力

    private final String[]          type = new String[] {
            Environment.DIRECTORY_DOCUMENTS,
            Environment.DIRECTORY_DOWNLOADS,
            Environment.DIRECTORY_PICTURES
    };
    private final File[]            path = new File[type.length];
    private File                    file;
    private final Context           context;
    :
    //コンストラクタ
    public ExternalStorageWriter(Context context) {
        this.context = context;
        setContext(context);
        for (int i=0; i< type.length; i++) {
            path[i] = context.getExternalFilesDir(type[i]);
        }
        propertiesFile = new File(context.getFilesDir(),String.format("%s.properties", context.getString(R.string.app_name)));
    }
    //ファイル指定
    public boolean setFile(String fileName, int type) {
        file = new File(path[type], fileName);
        return file.exists();
    }
  :
    //ファイル書込(bitmap)
    public void writeFileBitmap(Bitmap bitmap) {
        if (isExternalStorageWritable()) {
            try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
            } catch (Exception e) {
                Log.d(TAG, Objects.requireNonNull(e.getMessage()));
                e.printStackTrace();
            }
        }
    }

スマホ本体の外部ストレージをハンドリングする ExternalStorageWriter クラスです。
コンストラクタでファイルの出力先となる外部ストレージのパス(File)を、
getExternalFilesDir を使用して設定しています。
ファイル名と出力先区分で出力ファイルを指定して、
Bitmap を指定した出力ファイルにJPEG方式で圧縮して出力しています。

Intent使用して画像ファイルを指定する実装
Intent 使用して画像ファイルを指定する場合、
activityResultLauncher を使用して、指定した画像ファイルの Uri を受け取ります。

    public  String                  BACK_GROUND = "stocker.jpeg";
    private ImageView               imageView;
    public  ActionMenuView          actionMenuView;
    :
    private final ActivityResultLauncher<Intent> activityResultLauncher= registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    if (result.getData() != null) {
                        switch (itemId) {
                            :
                            case 6: // 背景
                                new ExternalStorageWriter(context).binaryFileCopy(result.getData().getData(), BACK_GROUND, 2);
                                ExternalStorageReader externalStorageReader = new ExternalStorageReader(context);
                                externalStorageReader.setFile(BACK_GROUND,2);
                                imageView.setImageBitmap(externalStorageReader.ReadFileBitmap());
                                break;
                            default:
                        }
                    }
                }
            });
            :
    private void createView() {
        // アクションメニュー
        actionMenuView = findViewById(R.id.menu);
        :
        actionMenuView.getMenu().add(Menu.NONE, 6, Menu.NONE, context.getString(R.string.menu_Background));
        : 
        actionMenuView.setOnMenuItemClickListener(menuItem -> {
                itemId = menuItem.getItemId();
                Intent intent;
                switch (itemId) {
                    :
                    case 6:
                        // 背景設定
                        intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                        intent.addCategory(Intent.CATEGORY_OPENABLE);
                        intent.setType("image/*");
                        activityResultLauncher.launch(intent);
                        break;
                     :
                }
            return false;
        });

今回は、ここまでです。

参考 : Exifinterface

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

◎これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪

無料でJava言語を学べるのは、かなり魅力的♪
でも、応募資格は35歳以下です

未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪

コメント欄

タイトルとURLをコピーしました