質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

2回答

267閲覧

Bitmapをjpegにして保存するときのエラー

masa-nakamura

総合スコア3

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2024/04/14 12:45

編集2024/04/15 23:46

実現したいこと

Androidのスマホ上で、Bitmapの画像データをjpeg形式にしてスマホ内のStrageに保存したい。

発生している問題・分からないこと

 Bitmapの画像データをjpegやpngにしてスマホに保存する方法について、いくつかのサイトを参考にさせていただいて下記のようなコードを書きましたが、buttonを押したところでエラーが起き、画像の保存はできません。エラーが出るのは、saveBitmap関数の中の val out = FileOutputStream(attachName) のところです。
Bitmapを何とかしてスマホに保存したいと思っており、どうかご教示いただければありがたいです。

エラーメッセージ

error

1Caused by: java.io.FileNotFoundException: /storage/emulated/0/BitmapTest/MyJpegFile.jpg: open failed: ENOENT (No such file or directory)

該当のソースコード

MainActivity

1package ・・・bitmaptest 2 3import・・・ 4 5class MainActivity : AppCompatActivity() { 6 override fun onCreate(savedInstanceState: Bundle?) { 7 super.onCreate(savedInstanceState) 8 setContentView(R.layout.activity_main) 9 10 val bmp = BitmapFactory.decodeResource(resources, R.drawable.my_photo) //my_photoはdrawable内に置いた画像ファイル 11 findViewById<Button>(R.id.button).setOnClickListener { 12 if(bmp !=null){ 13 saveBitmap(bmp!!) 14 } 15 } 16 } 17 18 private fun saveBitmap(saveImage: Bitmap) { 19 val SAVE_DIR = "/BitmapTest/" 20 val file = File(Environment.getExternalStorageDirectory().path + SAVE_DIR) 21 try { 22 if (!file.exists()) { 23 file.mkdir() 24 } 25 } catch (e: SecurityException) { 26 e.printStackTrace() 27 throw e 28 } 29 val fileName: String = "MyJpegFile.jpg" 30 val attachName = file.absolutePath + "/" + fileName 31 32 try { 33 val out = FileOutputStream(attachName) 34 saveImage.compress(CompressFormat.JPEG, 100, out) 35 out.flush() 36 out.close() 37 } catch (e: IOException) { 38 e.printStackTrace() 39 throw e 40 } 41 42 val values = ContentValues() 43 val contentResolver: ContentResolver = contentResolver 44 values.put(Images.Media.MIME_TYPE, "image/jpeg") 45 values.put(Images.Media.TITLE, fileName) 46 values.put("_data", attachName) 47 contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values) 48 } 49} 50

Manifest

1<?xml version="1.0" encoding="utf-8"?> 2<manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools"> 4 5 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 6 android:maxSdkVersion="32" /> 7 8 <application 9 android:allowBackup="true" 10 android:dataExtractionRules="@xml/data_extraction_rules" 11 android:fullBackupContent="@xml/backup_rules" 12 android:icon="@mipmap/ic_launcher" 13 android:label="@string/app_name" 14 android:roundIcon="@mipmap/ic_launcher_round" 15 android:supportsRtl="true" 16 android:theme="@style/Theme.BitmapTest" 17 tools:targetApi="31"> 18 <activity 19 android:name=".MainActivity" 20 android:exported="true"> 21 <intent-filter> 22 <action android:name="android.intent.action.MAIN" /> 23 24 <category android:name="android.intent.category.LAUNCHER" /> 25 </intent-filter> 26 </activity> 27 </application> 28</manifest>

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

似た問題について、”file.mkdir()"のあとに”file.createNewFile()"を付け加えると良いとの情報があった(https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10167272950) が、これを試すと別の種類のエラー(Caused by: java.io.IOException: Permission denied)が発生した。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2024/04/14 17:26

目的は、drawable 内の画像を ExternalStorageDirectory に出力することでしょうか。 それとも drawable 内の画像を contentResolver で Images.Media.EXTERNAL_CONTENT_URI に入れることでしょうか。 それとも、 ExternalStorageDirectory の画像を contentResolver で Images.Media.EXTERNAL_CONTENT_URI に入れることでしょうか。
masa-nakamura

2024/04/15 03:49

 コメントありがとうございます。  最終的な目的は、撮影した写真をBitmapの形にしたうえでトリミングなどの操作を行い、これをjpegなどの形でスマホに保存することです。写真を撮影したりトリミングする部分を入れるとコードが煩雑になるので、drawableから読み込むような形のコードで質問させていただきました。  また、初心者なもので、画像を「ExternalStorageDirectory に出力する」ことと「contentResolver で Images.Media.EXTERNAL_CONTENT_URI に入れる」ことの違いがよくわかっていません。後でフォトなどのアプリで表示したりPCに画像を取り込んだりしたいのですが、そのためにどちらを使ったほうがよいかなど、お教えいただければ大変ありがたいです。
jimbe

2024/04/15 05:55 編集

メディア ContentProvider に(ContentResolver を通じて)画像を登録するということは、 ContentProvider に画像データそのものを転送(copy)します。 URI だけを登録して読み込み時に URI のファイルを参照させるというわけではありませんので、ContentProvider に登録するのが目的なだけで画像は既にメモリにあるのならファイルを作る必要はありません。 以下は、 ContentProvider に情報を登録して、その情報から得られる OutputStream に画像データを送っています。(元画像も ContentProvider にある想定のため InputStream も ContentProvider から得ていますが。) [Android: アプリ外からアクセスできるように画像を保存する (対象範囲別ストレージ対応)] https://qiita.com/irgaly/items/70a0f0599d78eafc1136 この辺りの仕様・実装方法・必要権限等は Android のバージョンによって紆余曲折があり、結局どうすればいいのかがはっきりしていませんので苦労する所です ><
masa-nakamura

2024/04/15 14:32

 コメントどうもありがとうございます。しかし申し訳ありません、前のコメントの書き方が悪くて誤解されたように思います。  今回の目的は、「撮影した写真をBitmapの形にして」と書いてしまいましたが、実際には、撮影済の保存された写真をBitmapにするのではなくて、androidx.camera.view.PreviewView の画像をそのままBitmapにした上でトリミングして保存するということです。そのため、画像がすでにメモリにあるわけではありません。   こうした場合、いったん写真を保存してから加工するのではなく、初めからトリミングした形で写真を保存する(すなわち、プレビューの中の枠囲いされた部分のみの写真とする)ことはできない、あるいは困難なのでしょうか。  ちなみに、プレビューの画像をBitmapにしてこれをトリミングして新しいBitmapを作ることは問題なくできています。
jimbe

2024/04/15 18:43 編集

Bitmap というのが android.graphics.Bitmap のことで合っていれば、(カメラのデータを) Bitmap オブジェクトにした時点で「画像がメモリにある」ということになります。ですので先のコメントの一部は『ContentProvider に登録するのが目的なだけで画像は既にメモリに(Bitmapオブジェクトとして)あるのならファイルを作る必要はありません』ということでした。 Bitmap オブジェクトを操作することとそのデータがファイルに出力されたかどうかは関係ありません。 String の文字列を操作するのに txt ファイルに保存してからじゃないと出来ないわけでは無いことと同じです。
masa-nakamura

2024/04/15 23:44

>(カメラのデータを) Bitmap オブジェクトにした時点で「画像がメモリにある」ということになります  わかりました。そういうことなのですね。不勉強ですみません。m(__)m  ところで、紹介していただいたウェブサイトに掲載されている関数は、「Uriを指定した画像を読み込んで、これを外部ストレージに保存する」というものだと思いますが、ただのBitmapオブジェクトではUriがないので、ここにあるやり方は適用できないのではないかと考えましたが、この点はいかがでしょうか。
masa-nakamura

2024/04/15 23:52

また、いただいた『ContentProvider に登録するのが目的なだけで画像は既にメモリに(Bitmapオブジェクトとして)あるのならファイルを作る必要はありません』とのコメントは、「試したこと、調べたこと」の中で私が書いた「”file.mkdir()"のあとに”file.createNewFile()"を付け加える」ということは必要ないという趣旨(つまり、「該当のソースコード」に掲載したコードに問題が見つかったというわけではない)と受け取りましたが、これもそういうことでよろしいのでしょうか。
jimbe

2024/04/16 06:20 編集

不勉強とかで無く、オブジェクトがどう在るのかというイメージがまだ固まっていないのだと思います。イメージが固まれば各々を部品としてハッキリ見えて組み立てることが出来るようになりますので、それまでの辛抱でしょう。 紹介させていただいたリンクは、ざっくり言うなら ContentProvider 内の画像を ContentProvider 内にコピーするものです。 何故それをというなら、コピー元がどうあれ ContentProvider に書き込む部分は同じになるはずだからです。 「ContentProvider に書き込む部分」というのは、IS_PENDING=true としてinsert し、その Uri から OutputStream を取り出して InputStream から copy し、 IS_PENDING=false として update するという一連の処理です。 質問されている処理との違いは仰るように Bitmap を入れたいがそれには Uri は無いことですが、上に書いたように ContentProvider に書き込む為に最終的に必要なのは Uri では無く copy する際の InputStream です。 「いや InputStream も Bitmap に無い」と思われるのは当然ですが、もう一歩進むと「InputStream で無くても、 OutputStream に書き込めれば良い」ということにお気付きになるでしょうか。 気付ければ、 Bitmap から OutputStream への書き込みは既に行っていますから組み合わせることが出来るはずです。 java で読み難いかと思いますが単語を拾う感じで読んで頂けるなら [[Android] MediaStore 画像を保存する] https://akira-watson.com/android/mediastore-save.html のサンプルプログラムが drawable の画像を jpeg で登録しています。 >つまり、「該当のソースコード」に掲載したコードに問題が見つかったというわけではない 私の癖といいますか、何かで悩む時には『本当にそれを悩む必要があるのか』を考えてしまいます。 最終的な目的が ContentProvider に登録・保存して画像選択アプリ等から使えるようにすることであれば、私の知る限りはファイルの読み書きは必要が無く、また起きている問題も ContentProvider 関係ではありません。 従いましてご提示のコードの直接の問題点を説明しているのではありませんし、また一連の書き込みを『回答』にしていない理由です。
masa-nakamura

2024/04/16 08:17

 詳しい説明をありがとうございます。まだよくは理解していませんが、少しわかりそうな気がしています。ご説明を読みながら、紹介いただいたサイトのコードを読みたいと思います。ただ、ここ2日ほど時間があまりないので(コードを読むのに相当時間がかかるのです)、多分週末頃、結果を本欄に書かせていただきます。
jimbe

2024/04/17 05:34

プログラム書くよりも読むことのほうが圧倒的に多いですので、そのうち慣れて早くなりますよ^^
masa-nakamura

2024/04/20 03:10 編集

Jimbe様> 先日からいろいろとありがとうございます。紹介いただいたサイト等を参考にして、当初の目的どおり動くコードを作ることができました!! また、説明いただいたので、より理解してコードを書けた気がしています。なお、JavaのコードについてはAndroidStudioでKotlinに翻訳すると内容は難しくなく、案外簡単に読むことができました。  最初のコードの問題点は、Bitmapをcompressしてそのあとで名前等を登録しようとしていたこと(その登録のしかたにも問題がありましたが)と、compressの引数としてFileOutputStreamを使っていましたが、そうではなくてcontentResolver.openOutputStream()から得たoutputStreamを使わないといけないことだったと思います。    お陰様で解決したのでベストアンサーとさせていただきたいです。いただいたアドバイスの要旨について回答として投稿いただけないでしょうか。また、それにコメントとして、動くようになったコードをつけさせていただきたいです。
jimbe

2024/04/20 04:21

お疲れさまでした。 形としては「ContentProvider にパスを保存」ではなく「画像そのものを保存」するようにしなければならないということですね。 ContentProvider の存在自体が「どこにどう保存されているのかは分からないがとにかく Uri (と ContentResolver) さえあれば管理情報やデータ自体にアクセス出来る」ことを目的しているからです。 一応内部的には管理情報は SQLite でデータはファイルになっていると思いますが、それを外から見せないことで後々もっと良い方法になった時に使う側からは変化を意識しないで済むことになります。 ContentProvider 自体はユーザが自身のアプリ用に作ることも出来ますので、そのようなドキュメントを参照されるとより構造的な理解が出来るかと思います。 なお、コメントにはマークダウンは効きませんのでコードは書かないほうが良いです。 かと言って質問を編集するのも違う気がしますので、完成コードの公開は回答として書かれるのが良さそうです。
masa-nakamura

2024/04/20 10:28

>形としては「ContentProvider にパスを保存」ではなく「画像そのものを保存」するようにしなければならない  そういうことなのですね。また勉強したいと思います。 >完成コードの公開は回答として書かれるのが良さそうです。  そのようにさせていただきます。  本当に、いろいろとありがとうございました。
guest

回答2

0

 ベストアンサーでいただいたアドバイスをもとに、saveBitmap()関数を次のように書き換えたら動くようになりました。なお、簡単のためエラーへの対応(try...catchや変数がnullだった場合の対応など)を載せておりませんが、実際にアプリに組み込む際には使ったほうがいいかもしれません。

kotlin

1 private fun saveBitmap(saveImage: Bitmap) { 2 val values = ContentValues() 3 values.put(Images.Media.MIME_TYPE, "image/jpeg") 4 values.put(Images.Media.DISPLAY_NAME, "MyJpegFile.jpg") 5 values.put(Images.Media.IS_PENDING, true) 6 7 val item = contentResolver.insert( 8 Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), 9 values)!! 10 11 val out = contentResolver.openOutputStream(item)!! 12 saveImage.compress(CompressFormat.JPEG, 100, out) 13 out.close() 14 Toast.makeText(this, "Bitmapが保存されました", Toast.LENGTH_LONG).show() 15 values.clear() 16 values.put(Images.Media.IS_PENDING, false) 17 contentResolver.update(item, values,null,null) 18 }

投稿2024/04/20 08:49

編集2024/04/21 11:48
masa-nakamura

総合スコア3

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

後でフォトなどのアプリで表示したりPCに画像を取り込んだりしたい

ということですので、「ファイルを生成して保存」では無く「メディア ContentProvider に画像を登録する」方向での回答です。

メディア ContentProvider に(ContentResolver を通じて)画像を登録するということは、 ContentProvider に画像データそのものを転送(copy)します。 URI だけを登録して読み込み時に URI のファイルを参照させるというわけではありませんので、ContentProvider に登録するのが目的なだけで画像は既にメモリにあるのならファイルを作る必要はありません。

以下は、 ContentProvider に情報を登録して、その情報から得られる OutputStream に画像データを送っています。(元画像も ContentProvider にある想定のため InputStream も ContentProvider から得ていますが。)

Android: アプリ外からアクセスできるように画像を保存する (対象範囲別ストレージ対応)

また、 java で読み難いかと思いますが単語を拾う感じで読んで頂けるなら

[Android] MediaStore 画像を保存する

のサンプルプログラムが drawable の画像を jpeg で登録しています。

投稿2024/04/20 04:22

jimbe

総合スコア12659

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masa-nakamura

2024/04/20 08:44

 ありがとうございます。お陰様で問題が解決したのと、理解が深まりました。  別途、自己解決を投稿して、動くようになったコードを載せておきます。
jimbe

2024/04/20 09:35

ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問