1 package jp.osdn.gokigen.thetaview.liveview.storeimage
3 import android.content.ContentValues
4 import android.database.DatabaseUtils
5 import android.graphics.Bitmap
6 import android.os.Build
7 import android.os.Environment
8 import android.provider.MediaStore
9 import android.util.Log
10 import androidx.fragment.app.FragmentActivity
11 import jp.osdn.gokigen.thetaview.R
12 import jp.osdn.gokigen.thetaview.liveview.image.IImageProvider
13 import jp.osdn.gokigen.thetaview.preference.IPreferencePropertyAccessor
14 import jp.osdn.gokigen.thetaview.preference.PreferenceAccessWrapper
15 import java.io.ByteArrayOutputStream
18 import java.io.FileOutputStream
19 import java.text.SimpleDateFormat
22 class StoreImage(private val context: FragmentActivity, private val imageProvider: IImageProvider, private val dumpLog : Boolean = false) : IStoreImage
25 override fun doStore(target: Bitmap?)
29 // 保存処理(プログレスダイアログ(「保存中...」)を表示
31 val bitmapToStore = target ?: imageProvider.getImage()
32 val isLocalLocation = PreferenceAccessWrapper(context).getBoolean(IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION, IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE)
35 saveImageLocal(bitmapToStore)
39 saveImageExternal(bitmapToStore)
42 // 保存処理(プログレスダイアログ(「保存中...」)を削除
51 private fun storeImageImpl(target: Bitmap)
54 // 保存処理(プログレスダイアログ(「保存中...」)を表示して処理する)
55 val saveDialog = ProgressDialog(context)
56 saveDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER)
57 saveDialog.setMessage(context!!.getString(R.string.data_saving))
58 saveDialog.isIndeterminate = true
59 saveDialog.setCancelable(false)
79 val isLocalLocation = PreferenceAccessWrapper(context).getBoolean(IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION, IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE)
82 saveImageLocal(target)
86 saveImageExternal(target)
95 private fun prepareLocalOutputDirectory(): File
97 val mediaDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
99 return (if (mediaDir != null && mediaDir.exists()) mediaDir else context.filesDir)
103 * ビットマップイメージをファイルに出力する
105 * @param targetImage 出力するビットマップイメージ
107 private fun saveImageLocal(targetImage: Bitmap)
111 val fileName = "L" + SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg"
112 val photoFile = File(prepareLocalOutputDirectory(), fileName)
113 val outputStream = FileOutputStream(photoFile)
114 targetImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
124 private fun getExternalOutputDirectory(): File
126 @Suppress("DEPRECATION") val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + "/" + context.getString(R.string.app_location) + "/"
127 val target = File(directoryPath)
138 Log.v(TAG, " ----- RECORD Directory PATH : $directoryPath -----")
144 * ビットマップイメージを外部ストレージのファイルに出力する
146 * @param targetImage 出力するビットマップイメージ
148 private fun saveImageExternal(targetImage: Bitmap)
152 if (!isExternalStorageWritable())
155 saveImageLocal(targetImage)
159 val outputDir = getExternalOutputDirectory()
160 val resolver = context.contentResolver
161 val mimeType = "image/jpeg"
162 val now = System.currentTimeMillis()
163 val path = Environment.DIRECTORY_DCIM + File.separator + context.getString(R.string.app_location) // Environment.DIRECTORY_PICTURES + File.separator + "gokigen" //"DCIM/aira01a/"
164 val fileName = "L" + SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(now) + ".jpg"
166 val values = ContentValues()
167 values.put(MediaStore.Images.Media.TITLE, fileName)
168 values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
169 values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
170 val extStorageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
171 values.put(MediaStore.Images.Media.RELATIVE_PATH, path)
173 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
174 values.put(MediaStore.Images.Media.XMP, xmpValue)
177 values.put(MediaStore.Images.Media.IS_PENDING, true)
178 MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
180 @Suppress("DEPRECATION")
181 values.put(MediaStore.Images.Media.DATA, outputDir.absolutePath + File.separator + fileName)
182 MediaStore.Images.Media.EXTERNAL_CONTENT_URI
184 val imageUri = resolver.insert(extStorageUri, values)
185 if (imageUri != null)
187 //resolver.update(imageUri, values, null, null)
188 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
190 Log.v(TAG, " ===== StorageOperationWithPermission() : $imageUri =====")
191 //StorageOperationWithPermission(context).requestAndroidRMediaPermission(imageUri)
194 ////////////////////////////////////////////////////////////////
197 val cursor = resolver.query(imageUri, null, null, null, null)
198 DatabaseUtils.dumpCursor(cursor)
201 ////////////////////////////////////////////////////////////////
203 val outputStream = resolver.openOutputStream(imageUri)
204 if (outputStream != null)
206 val xmpValue = "http://ns.adobe.com/xap/1.0/"
207 val xmpValue1 = "<?xpacket begin=\"" + "\" ?> <x:xmpmeta xmlns:x=\"adobe:ns:meta/\" xmptk=\"ThetaThoughtShutter\">" +
208 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"><rdf:Description rdf:about=\"\" xmlns:GPano=\"http://ns.google.com/photos/1.0/panorama/\">" +
209 "<GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer><GPano:ProjectionType>equirectangular</GPano:ProjectionType>" +
210 "<GPano:FullPanoWidthPixels>${targetImage.width}</GPano:FullPanoWidthPixels><GPano:FullPanoHeightPixels>${targetImage.height}</GPano:FullPanoHeightPixels>" +
211 "<GPano:CroppedAreaImageWidthPixels>${targetImage.width}</GPano:CroppedAreaImageWidthPixels><GPano:CroppedAreaImageHeightPixels>${targetImage.height}</GPano:CroppedAreaImageHeightPixels>"+
212 "<GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels><GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end=\"r\"?>"
213 val xmpLength = xmpValue.length + xmpValue1.length + 3
214 val byteArrayStream = ByteArrayOutputStream()
215 targetImage.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayStream)
216 val jpegImage = byteArrayStream.toByteArray()
218 outputStream.write(jpegImage, 0, 20)
219 outputStream.write(0xff)
220 outputStream.write(0xe1)
221 outputStream.write((xmpLength and 0xff00) shr 8)
222 outputStream.write((xmpLength and 0x00ff))
223 outputStream.write(xmpValue.toByteArray())
224 outputStream.write(0x00)
225 outputStream.write(xmpValue1.toByteArray())
226 outputStream.write(jpegImage, 20, (jpegImage.size -20))
228 //targetImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
232 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
234 values.put(MediaStore.Images.Media.IS_PENDING, false)
235 resolver.update(imageUri, values, null, null)
246 private fun isExternalStorageWritable(): Boolean
248 return (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
253 private val TAG = this.toString()
254 private const val FILENAME_FORMAT = "yyyyMMdd_HHmmss"