Skip to content

Commit aa93701

Browse files
empratyushthestinger
authored andcommitted
exif fixes & code cleanup
move clearing exif stuff from onImageSaved to when first time image get saved and and fix geo-tagging, save as previewed regression caused in v25
1 parent 20b6847 commit aa93701

File tree

4 files changed

+51
-81
lines changed

4 files changed

+51
-81
lines changed

app/src/main/java/app/grapheneos/camera/ExifHelper.kt

+10-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package app.grapheneos.camera
22

3-
import android.content.Context
4-
import android.net.Uri
53
import androidx.exifinterface.media.ExifInterface
64
import app.grapheneos.camera.ui.activities.MainActivity.Companion.camConfig
75
import java.util.TimeZone
@@ -166,15 +164,7 @@ private val exifAttributes = arrayOf(
166164
ExifInterface.TAG_SUBFILE_TYPE,
167165
)
168166

169-
fun fixExif(context: Context, uri: Uri) {
170-
171-
val inStream = context.contentResolver.openFileDescriptor(
172-
uri,
173-
"rw"
174-
)!!
175-
176-
val exifInterface = ExifInterface(inStream.fileDescriptor)
177-
167+
fun ExifInterface.fixExif(): ExifInterface {
178168
val now = Date()
179169

180170
val millis = TimeZone.getDefault().getOffset(now.time)
@@ -188,41 +178,26 @@ fun fixExif(context: Context, uri: Uri) {
188178
hoursStrRep = "+${hoursStrRep}"
189179

190180
val mins = (totalMins % 60).toString().padEnd(2, '0')
191-
192181
val offsetTime = "$hoursStrRep:$mins"
193182

194-
exifInterface.setAttribute(ExifInterface.TAG_OFFSET_TIME, offsetTime)
195-
exifInterface.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, offsetTime)
183+
setAttribute(ExifInterface.TAG_OFFSET_TIME, offsetTime)
184+
setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, offsetTime)
196185
// exifInterface.setAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED, offset_time)
197186

198187
val nowStrRep = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US).format(now)
199188

200-
exifInterface.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, nowStrRep)
201-
exifInterface.setAttribute(ExifInterface.TAG_DATETIME, nowStrRep)
202-
203-
exifInterface.saveAttributes()
204-
205-
inStream.close()
189+
setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, nowStrRep)
190+
setAttribute(ExifInterface.TAG_DATETIME, nowStrRep)
191+
return this
206192
}
207193

208-
fun clearExif(context: Context, uri: Uri) {
209-
210-
if (!camConfig.removeExifAfterCapture) return
211-
212-
val inStream = context.contentResolver.openFileDescriptor(
213-
uri,
214-
"rw"
215-
)!!
216-
217-
val exifInterface = ExifInterface(inStream.fileDescriptor)
194+
fun ExifInterface.clearExif(): ExifInterface {
195+
if (!camConfig.removeExifAfterCapture) return this
218196

219197
for (exifTag in exifAttributes) {
220-
exifInterface.removeAttribute(exifTag)
198+
removeAttribute(exifTag)
221199
}
222-
223-
exifInterface.saveAttributes()
224-
225-
inStream.close()
200+
return this
226201
}
227202

228203
// TODO: (Re-)use this code later to implement custom EXIF removal setting

app/src/main/java/app/grapheneos/camera/capturer/ImageCapturer.kt

+2-11
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import app.grapheneos.camerax.OutputFileOptions
2424
import app.grapheneos.camera.App
2525
import app.grapheneos.camera.CamConfig
2626
import app.grapheneos.camera.R
27-
import app.grapheneos.camera.clearExif
28-
import app.grapheneos.camera.fixExif
2927
import app.grapheneos.camera.ui.activities.MainActivity
3028
import app.grapheneos.camera.ui.activities.MainActivity.Companion.camConfig
3129
import app.grapheneos.camera.ui.activities.SecureMainActivity
@@ -92,12 +90,10 @@ class ImageCapturer(private val mActivity: MainActivity) {
9290
fileName
9391
)!!
9492

95-
val oStream = mActivity.contentResolver
96-
.openOutputStream(child.uri)!!
97-
93+
val contentResolver = mActivity.contentResolver
9894
camConfig.addToGallery(child.uri)
9995

100-
return OutputFileOptions.OutputFileOptionsOutputStream(oStream)
96+
return OutputFileOptions.OutputFileOptionsOutputStream(contentResolver, child.uri)
10197
} catch (exception: NullPointerException) {
10298
throw FileNotFoundException("The default storage location seems to have been deleted.")
10399
}
@@ -159,11 +155,6 @@ class ImageCapturer(private val mActivity: MainActivity) {
159155
mActivity.capturedFilePaths.add(0, camConfig.latestUri.toString())
160156
}
161157

162-
camConfig.latestUri?.let {
163-
fixExif(mActivity, it)
164-
clearExif(mActivity, it)
165-
}
166-
167158
mActivity.runOnUiThread {
168159
camConfig.updatePreview()
169160
mActivity.previewLoader.visibility = View.GONE

app/src/main/java/app/grapheneos/camerax/ImageSaver.kt

+36-34
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
1414
import androidx.camera.core.internal.utils.ImageUtil
1515
import androidx.camera.core.internal.utils.ImageUtil.CodecFailedException
1616
import androidx.camera.core.internal.utils.ImageUtil.CodecFailedException.FailureType
17+
import androidx.exifinterface.media.ExifInterface
18+
import app.grapheneos.camera.clearExif
19+
import app.grapheneos.camera.fixExif
1720
import app.grapheneos.camerax.OutputFileOptions.OutputFileOptionsMediaStore
18-
import app.grapheneos.camerax.OutputFileOptions.OutputFileOptionsOutputStream
1921
import java.io.IOException
20-
import java.io.OutputStream
2122
import java.io.UnsupportedEncodingException
2223
import java.util.concurrent.Executor
2324
import java.util.concurrent.RejectedExecutionException
@@ -51,18 +52,14 @@ class ImageSaver(
5152
is OutputFileOptionsMediaStore -> {
5253
val (contentResolver, saveCollection, values) = mOutputFileOptions
5354
setContentValuePending(values, pending)
54-
val outputUri = contentResolver.insert(
55-
saveCollection,
56-
values
57-
)
58-
val output: OutputStream = contentResolver.openOutputStream(outputUri!!)!!
59-
saveException = output.writeImageAndExif()
55+
val outputUri = contentResolver.insert(saveCollection, values)!!
56+
saveException = writeImageAndExif(contentResolver, outputUri)
6057
setUriNotPending(outputUri, contentResolver)
6158
uri = outputUri
6259
}
63-
is OutputFileOptionsOutputStream -> {
64-
val (outputStream) = mOutputFileOptions
65-
saveException = outputStream.writeImageAndExif()
60+
is OutputFileOptions.OutputFileOptionsOutputStream -> {
61+
val (contentResolver, outputUri) = mOutputFileOptions
62+
saveException = writeImageAndExif(contentResolver, outputUri)
6663
}
6764
}
6865
} catch (e: IOException) {
@@ -96,33 +93,38 @@ class ImageSaver(
9693
val exception: Exception? = null,
9794
)
9895

99-
private fun OutputStream.writeImageAndExif(): SaveException? {
96+
private fun writeImageAndExif(
97+
contentResolver: ContentResolver, uri: Uri
98+
): SaveException? {
10099
var saveException: SaveException? = null
101100
try {
102-
val bytes = imageToJpegByteArray(mImage, mJpegQuality)
103-
write(bytes)
104-
105-
// Create new exif based on the original exif.
106-
val exif = Exif.createFromImageProxy(mImage)
107-
Exif.createFromImageProxy(mImage).copyToCroppedImage(exif)
108-
109-
// Overwrite the original orientation if the quirk exists.
110-
if (!ExifRotationAvailability().shouldUseExifOrientation(mImage)) {
111-
exif.rotate(mOrientation)
112-
}
113-
114-
// Overwrite exif based on metadata.
115101
val metadata = mOutputFileOptions.metadata
116-
if (metadata.isReversedHorizontal) {
117-
exif.flipHorizontally()
118-
}
119-
if (metadata.isReversedVertical) {
120-
exif.flipVertically()
102+
contentResolver.openOutputStream(uri)?.use { os ->
103+
val bytes = imageToJpegByteArray(mImage, mJpegQuality)
104+
os.write(bytes)
121105
}
122-
if (metadata.location != null) {
123-
exif.attachLocation(metadata.location!!)
124-
}
125-
exif.save()
106+
107+
contentResolver.openAssetFileDescriptor(uri, "rw")?.createInputStream()
108+
?.use { inputStream ->
109+
val exifSavedImage = Exif.createFromInputStream(inputStream)
110+
val exifCapturedImage = Exif.createFromImageProxy(mImage)
111+
exifCapturedImage.copyToCroppedImage(exifSavedImage)
112+
113+
metadata.location?.let { exifSavedImage.attachLocation(it) }
114+
if (metadata.isReversedHorizontal) {
115+
exifSavedImage.flipHorizontally()
116+
}
117+
if (metadata.isReversedVertical) {
118+
exifSavedImage.flipVertically()
119+
}
120+
if (!ExifRotationAvailability().shouldUseExifOrientation(mImage)) {
121+
exifSavedImage.rotate(mOrientation)
122+
}
123+
exifSavedImage.save()
124+
}
125+
contentResolver.openAssetFileDescriptor(uri, "rw")?.createInputStream()?.use {
126+
ExifInterface(it).fixExif().clearExif().saveAttributes()
127+
}
126128
} catch (e: IOException) {
127129
saveException = SaveException(
128130
SaveError.FILE_IO_FAILED,

app/src/main/java/app/grapheneos/camerax/OutputFileOptions.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app.grapheneos.camerax
22

33
import android.content.ContentResolver
44
import android.content.ContentValues
5+
import android.content.res.AssetFileDescriptor
56
import android.net.Uri
67
import androidx.camera.core.ImageCapture
78
import java.io.OutputStream
@@ -18,7 +19,8 @@ sealed class OutputFileOptions(
1819
) : OutputFileOptions(metadata)
1920

2021
data class OutputFileOptionsOutputStream(
21-
val outputStream: OutputStream,
22+
val contentResolver: ContentResolver,
23+
val uri: Uri,
2224
override var metadata: ImageCapture.Metadata = ImageCapture.Metadata()
2325
) : OutputFileOptions(metadata)
2426

0 commit comments

Comments
 (0)