Mercurial > cgi-bin > hgweb.cgi > SimpleResizer
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 } |