comparison app/src/main/java/com/bartsent/simpleresizer/EditImage.kt @ 18:eedf995462d9 concur2

Parallalized, but ConstraintLayout started hosing the edit window.
author David Barts <n5jrn@me.com>
date Mon, 22 Feb 2021 07:04:31 -0800
parents 3ed74dc0e34a
children 7e7e71724770
comparison
equal deleted inserted replaced
17:86740f593b6c 18:eedf995462d9
26 import com.bartsent.simpleresizer.lib.getScaledInstance 26 import com.bartsent.simpleresizer.lib.getScaledInstance
27 import java.io.File 27 import java.io.File
28 import java.io.IOException 28 import java.io.IOException
29 import kotlin.concurrent.thread 29 import kotlin.concurrent.thread
30 import androidx.preference.PreferenceManager 30 import androidx.preference.PreferenceManager
31 import com.bartsent.simpleresizer.lib.ThreadPools
31 32
32 class EditImage : AppCompatActivity() { 33 class EditImage : AppCompatActivity() {
33 private object State { 34 private object State {
34 var uri: Uri? = null 35 var uri: Uri? = null
35 var bitmap: Bitmap? = null 36 var bitmap: Bitmap? = null
106 } 107 }
107 108
108 // Being stateful stops data loss when the phone gets rotated. 109 // Being stateful stops data loss when the phone gets rotated.
109 if (imageUri != State.uri) { 110 if (imageUri != State.uri) {
110 State.uri = imageUri 111 State.uri = imageUri
111 State.bitmap = contentResolver.openInputStream(imageUri).use { 112 binding.progressBar.visibility = ProgressBar.VISIBLE
112 BitmapFactory.decodeStream(it) 113 ThreadPools.WORKERS.execute {
113 } 114 State.bitmap = contentResolver.openInputStream(imageUri).use {
114 } 115 BitmapFactory.decodeStream(it)
115 if (State.bitmap == null) { 116 }
116 showFatalError(getString(R.string.error_bad_image)) 117 runOnUiThread {
117 return 118 binding.progressBar.visibility = ProgressBar.INVISIBLE
118 } 119 if (State.bitmap == null)
119 setImage(State.bitmap!!) 120 showFatalError(getString(R.string.error_bad_image))
120 } 121 else
121 122 setImage(State.bitmap!!)
122 fun setImage(image: Bitmap): Unit { 123 }
124 }
125 }
126 }
127
128 private fun setImage(image: Bitmap): Unit {
123 binding.imageSize.text = getString(R.string.image_size_text, image.width, image.height) 129 binding.imageSize.text = getString(R.string.image_size_text, image.width, image.height)
124 binding.image.setImageBitmap(image) 130 binding.image.setImageBitmap(image)
125 State.bitmap = image 131 State.bitmap = image
132 binding.root.invalidate()
126 } 133 }
127 134
128 private val CUSTOM = 999998 135 private val CUSTOM = 999998
129 private val CANCEL = 999999 136 private val CANCEL = 999999
130 137
145 setOnMenuItemClickListener(::scaleMenuItemClicked) 152 setOnMenuItemClickListener(::scaleMenuItemClicked)
146 show() 153 show()
147 } 154 }
148 } 155 }
149 156
150 fun scaleMenuItemClicked(item: MenuItem) : Boolean = 157 private fun scaleMenuItemClicked(item: MenuItem) : Boolean =
151 when (item.itemId) { 158 when (item.itemId) {
152 CUSTOM -> { showCustomScaleDialog(); true } 159 CUSTOM -> { showCustomScaleDialog(); true }
153 CANCEL -> true 160 CANCEL -> true
154 in STDDIMS -> { doScale(item.itemId); true } 161 in STDDIMS -> { doScale(item.itemId); true }
155 else -> false 162 else -> false
161 if (oldColorSpace != null) 168 if (oldColorSpace != null)
162 new.setColorSpace(oldColorSpace) 169 new.setColorSpace(oldColorSpace)
163 } 170 }
164 } 171 }
165 172
166 fun doScale(newMax: Int): Unit { 173 private fun doScale(newMax: Int): Unit {
167 val oldBitmap = State.bitmap!! 174 val oldBitmap = State.bitmap!!
168 val factor = newMax.toDouble() / maxOf(oldBitmap.width, oldBitmap.height).toDouble() 175 val factor = newMax.toDouble() / maxOf(oldBitmap.width, oldBitmap.height).toDouble()
169 if (factor >= 1.0) { 176 if (factor >= 1.0) {
170 throw IllegalArgumentException("can only scale down") 177 throw IllegalArgumentException("can only scale down")
171 } 178 }
172 val scaleType = PreferenceManager.getDefaultSharedPreferences(applicationContext).getString( 179 val scaleType = PreferenceManager.getDefaultSharedPreferences(applicationContext).getString(
173 "scale_type", "speed" ) 180 "scale_type", "speed" )
174 Log.d("EditImage", "scaling, scale_type = $scaleType") 181 Log.d("EditImage", "scaling, scale_type = $scaleType")
175 binding.progressBar.visibility = ProgressBar.VISIBLE 182 binding.progressBar.visibility = ProgressBar.VISIBLE
176 thread { 183 ThreadPools.WORKERS.execute {
177 val newWidth = (oldBitmap.width.toDouble() * factor + 0.5).toInt() 184 val newWidth = (oldBitmap.width.toDouble() * factor + 0.5).toInt()
178 val newHeight = (oldBitmap.height.toDouble() * factor + 0.5).toInt() 185 val newHeight = (oldBitmap.height.toDouble() * factor + 0.5).toInt()
179 val newBitmap = if (scaleType == "quality") 186 val newBitmap = if (scaleType == "quality")
180 oldBitmap.getScaledInstance(newWidth, newHeight) 187 oldBitmap.getScaledInstance(newWidth, newHeight)
181 else 188 else
185 setImage(newBitmap) 192 setImage(newBitmap)
186 } 193 }
187 } 194 }
188 } 195 }
189 196
190 fun showCustomScaleDialog(): Unit { 197 private fun showCustomScaleDialog(): Unit {
191 val image = State.bitmap!! 198 val image = State.bitmap!!
192 val curMaxDim = maxOf(image.width, image.height) 199 val curMaxDim = maxOf(image.width, image.height)
193 val dialogView = layoutInflater.inflate(R.layout.dialog_custom_scale, null) 200 val dialogView = layoutInflater.inflate(R.layout.dialog_custom_scale, null)
194 AlertDialog.Builder(this).also { 201 AlertDialog.Builder(this).also {
195 it.setPositiveButton(R.string.ok_text) { dialog, _ -> 202 it.setPositiveButton(R.string.ok_text) { dialog, _ ->
232 inflate(R.menu.rotate) 239 inflate(R.menu.rotate)
233 show() 240 show()
234 } 241 }
235 } 242 }
236 243
237 fun doRotate(deg: Int): Unit { 244 private fun doRotate(deg: Int): Unit {
238 val oldBitmap = State.bitmap!! 245 val oldBitmap = State.bitmap!!
239 if (deg % 90 != 0) { 246 if (deg % 90 != 0) {
240 throw IllegalArgumentException("$deg not a multiple of 90") 247 throw IllegalArgumentException("$deg not a multiple of 90")
241 } 248 }
242 val (w, h) = if (deg % 180 == 0) Pair(oldBitmap.width, oldBitmap.height) else Pair(oldBitmap.height, oldBitmap.width) 249 val (w, h) = if (deg % 180 == 0) Pair(oldBitmap.width, oldBitmap.height) else Pair(oldBitmap.height, oldBitmap.width)
243 val rotater = Matrix().apply { 250 val rotater = Matrix().apply {
244 setRotate(deg.toFloat(), oldBitmap.width.toFloat()/2.0f, oldBitmap.height.toFloat()/2.0f) 251 setRotate(deg.toFloat(), oldBitmap.width.toFloat()/2.0f, oldBitmap.height.toFloat()/2.0f)
245 postTranslate((w - oldBitmap.width).toFloat()/2.0f, (h - oldBitmap.height).toFloat()/2.0f) 252 postTranslate((w - oldBitmap.width).toFloat()/2.0f, (h - oldBitmap.height).toFloat()/2.0f)
246 } 253 }
247 binding.progressBar.visibility = ProgressBar.VISIBLE 254 binding.progressBar.visibility = ProgressBar.VISIBLE
248 thread { 255 ThreadPools.WORKERS.execute {
249 val newBitmap = Bitmap.createBitmap(w, h, oldBitmap.config) 256 val newBitmap = Bitmap.createBitmap(w, h, oldBitmap.config)
250 copyColorSpace(oldBitmap, newBitmap) 257 copyColorSpace(oldBitmap, newBitmap)
251 Canvas(newBitmap).run { 258 Canvas(newBitmap).run {
252 drawBitmap(oldBitmap, rotater, null) 259 drawBitmap(oldBitmap, rotater, null)
253 } 260 }
285 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { 292 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
286 put(MediaStore.MediaColumns.RELATIVE_PATH, 293 put(MediaStore.MediaColumns.RELATIVE_PATH,
287 File(Environment.DIRECTORY_PICTURES, getString(R.string.app_name)).getPath()) 294 File(Environment.DIRECTORY_PICTURES, getString(R.string.app_name)).getPath())
288 } 295 }
289 } 296 }
290 try { 297 binding.progressBar.visibility = ProgressBar.VISIBLE
291 val myUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) 298 ThreadPools.WORKERS.execute {
292 if (myUri == null) { 299 var errorMessage: String? = null
293 throw IOException(getString(R.string.error_create_mediastore)) 300 try {
294 } 301 val myUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
295 val stream = contentResolver.openOutputStream(myUri) 302 if (myUri == null) {
296 if (stream == null) { 303 throw IOException(getString(R.string.error_create_mediastore))
297 throw IOException(getString(R.string.error_get_output)) 304 }
298 } 305 val stream = contentResolver.openOutputStream(myUri)
299 val quality = maxOf(0, minOf(100, 306 if (stream == null) {
300 PreferenceManager.getDefaultSharedPreferences(applicationContext).getInt( 307 throw IOException(getString(R.string.error_get_output))
301 "jpeg_quality", 85))) 308 }
302 Log.d("EditImage", "saving, jpeg_quality = $quality") 309 val quality = maxOf(0, minOf(100,
303 stream.use { 310 PreferenceManager.getDefaultSharedPreferences(applicationContext).getInt(
304 if (!State.bitmap!!.compress(Bitmap.CompressFormat.JPEG, quality, it)) { 311 "jpeg_quality", 85)))
305 throw IOException(getString(R.string.error_save_bitmap)) 312 Log.d("EditImage", "saving, jpeg_quality = $quality")
306 } 313 stream.use {
307 } 314 if (!State.bitmap!!.compress(Bitmap.CompressFormat.JPEG, quality, it)) {
308 } catch (ioe: IOException) { 315 throw IOException(getString(R.string.error_save_bitmap))
309 Toast.makeText(applicationContext, ioe.message ?: getString(R.string.error_io), Toast.LENGTH_LONG).show() 316 }
310 } 317 }
311 finish() 318 } catch (ioe: IOException) {
319 errorMessage = ioe.message ?: getString(R.string.error_io)
320 }
321 runOnUiThread {
322 binding.progressBar.visibility = ProgressBar.INVISIBLE
323 if (errorMessage == null)
324 finish()
325 else
326 Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_LONG).show()
327 }
328 }
312 } 329 }
313 } 330 }