Compare commits

...
28 Commits
Author SHA1 Message Date
Markus Fisch 565643f19e Advance version number to 1.32.0 2020-06-23 21:31:37 +02:00
Markus Fisch 4c92045563 Clean up permission handling 2020-06-23 21:12:38 +02:00
Markus Fisch 7116aa7278 Add an action to remove a scan from result view
So there's no need for a long tap to remove individual scans.
2020-06-23 21:10:21 +02:00
Markus Fisch 860e867ad4 Fix restoring crop handle position 2020-06-23 16:57:16 +02:00
Markus Fisch 3b509cd09c Make tests fail if there are no/wrong samples 2020-06-22 14:54:53 +02:00
Markus Fisch 51ec228fd8 Remove unused quantities from translations 2020-06-22 14:35:48 +02:00
Markus Fisch 9d1dec146d Add bulk mode
Scan continuously to add items to the history (if enabled)
instead of showing the result screen.
2020-06-22 14:30:51 +02:00
Markus Fisch 7bd9fe36fe Handle storing/restoring crop handle like zoom 2020-06-22 13:21:40 +02:00
Markus Fisch 85dba0604c Make ROI handle position permanent
So a user doesn't have to restore it every time.
2020-06-22 11:17:38 +02:00
Markus Fisch 56c70f5974 Make default ROI bigger and always a square 2020-06-22 10:27:31 +02:00
Markus Fisch 098b7740a0 Give ROI handle precedence over other controls
Because it can easily overlap with other controls and would
then be inaccessible.
2020-06-22 10:19:42 +02:00
Markus Fisch 7d92481fa6 Update ROI immediately when handle is tapped 2020-06-22 10:09:18 +02:00
zmniandGitHub 2e285a5b1d Update Indonesian translation (#123) 2020-06-21 16:36:45 +02:00
Markus Fisch ad25c4681f Calculate default ROI according to orientation
So it's probably a better fit.
2020-06-18 17:08:36 +02:00
Markus Fisch c00c87ceca Add missing changelog files for f-droid 2020-06-18 09:55:08 +02:00
Markus Fisch e001a92870 Run detection in pick activity in background too
Because the TRY_HARDER flag is always set when scanning still
images, ZXing may take quite some time on slow devices. So it's
better to run it in a background thread.
2020-06-15 16:53:03 +02:00
Markus Fisch 7a32b00e1f Add a menu item to export generated barcode as SVG
And use clear icons for PNG and SVG export.
2020-06-11 16:04:00 +02:00
Markus Fisch 50cedf782d Automatically convert JPEG test samples to YUV
And convert the existing test samples from YUV to JPEG.

JPEGs are much smaller and it's much easier to work with them.
2020-06-10 14:28:31 +02:00
Markus Fisch fb0d22198f Refactor rotator to just rotate
To match the intrinsic names.

Also move the code into the same namespace as Processor so
we don't need the explicit import.
2020-06-09 15:20:33 +02:00
Markus Fisch af528df448 Advance version number to 1.31.0 2020-06-07 16:19:23 +02:00
Markus Fisch a9b5771d57 Fix initializing preprocessor for device tests 2020-06-07 16:19:08 +02:00
Markus Fisch a161e8fc0f Remove app name from polish translation
Because the app name isn't part of the default locale but the
same for all languages. Lint will fail otherwise.
2020-06-07 16:07:54 +02:00
Markus Fisch ec17d07695 Reset ROI when handle snaps back in place
And make sure ROI dimensions are never smaller than 4.
2020-06-07 15:51:53 +02:00
Karol KosekandGitHub be2fc2f559 Add Polish translation 2020-06-07 15:19:00 +02:00
Markus Fisch de3aa752ff Refactor data namespace to database
It's better to be explicit.
2020-06-04 09:19:50 +02:00
Markus Fisch 411cbb243f Clean up app initialization
This got a bit mixed up by all the special RenderScript handling.
2020-06-04 09:13:33 +02:00
Markus Fisch f76c1521fe Stop detection when ROI is modified
Avoids accidently detecting a barcode while a user is about
to change the region of interest.
2020-06-03 22:49:01 +02:00
Markus Fisch 9b9224b100 Optimize crop button drawable with avocado 2020-06-03 22:39:42 +02:00
69 changed files with 662 additions and 1212 deletions
+13
View File
@@ -1,5 +1,18 @@
# Change Log
## 1.32.0
* Add bulk mode
* Remember region of interest permanently
* Export generated barcode as SVG
* Run detection on images in background
* Remove a scan from result view
* Update Indonesian translation
## 1.31.0
* Stop detection while region of interest is modified
* Fix resetting region of interest
* Add Polish translation
## 1.30.1
* Improve usability of cropping limiter
* Draw round corners around region of interest
+2 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 67
versionName '1.30.1'
versionCode 69
versionName '1.32.0'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,14 +1,14 @@
package de.markusfisch.android.binaryeye
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.zxing.Zxing
import org.junit.Assert.fail;
import org.junit.Test
import org.junit.runner.RunWith
import java.util.regex.Pattern
@RunWith(AndroidJUnit4::class)
@@ -19,22 +19,27 @@ class PreprocessorTest {
val assets = InstrumentationRegistry.getInstrumentation()
.context.assets
val pattern = Pattern.compile(
"[0-9]+-([0-9]+)x([0-9]+)-([0-9]+)deg.yuv"
"[0-9]+-([0-9]+)deg.jpg"
)
val files = assets.list("yuv") ?: return
for (file in files) {
val m = pattern.matcher(file)
if (!m.find() || m.groupCount() < 3) {
continue
val samples = assets.list("samples")
checkNotNull(samples) { "no samples found" }
for (sample in samples) {
val m = pattern.matcher(sample)
if (!m.find() || m.groupCount() < 1) {
fail("invalid sample: $sample")
}
val frameWidth = m.group(1) ?: return
val frameHeight = m.group(2) ?: return
val frameOrientation = m.group(3) ?: return
val frameData = assets.open("yuv/$file").readBytes()
val bitmap = BitmapFactory.decodeStream(
assets.open("samples/$sample")
)
val frameData = getNV21(bitmap)
val frameWidth = bitmap.width
val frameHeight = bitmap.height
val frameOrientation = m.group(1) ?: return
val preprocessor = Preprocessor(
InstrumentationRegistry.getTargetContext(),
frameWidth.toInt(),
frameHeight.toInt()
frameWidth,
frameHeight,
null
)
val outWidth: Int
val outHeight: Int
@@ -51,7 +56,48 @@ class PreprocessorTest {
val result = zxing.decode(frameData, outWidth, outHeight)
preprocessor.destroy()
checkNotNull(result) { "no barcode found in $file" }
checkNotNull(result) { "no barcode found in $sample" }
}
}
}
fun getNV21(bitmap: Bitmap): ByteArray {
val width = bitmap.width
val height = bitmap.height
val argb = IntArray(width * height)
bitmap.getPixels(argb, 0, width, 0, 0, width, height)
return encodeYUV420SP(argb, width, height)
}
private fun encodeYUV420SP(argb: IntArray, width: Int, height: Int): ByteArray {
val yuv = ByteArray(ceilIfUneven(width) * ceilIfUneven(height) * 3 / 2)
var yIndex = 0
var uvIndex = width * height
var r: Int
var g: Int
var b: Int
var Y: Int
var u: Int
var v: Int
var index = 0
for (y in 0 until height) {
for (x in 0 until width) {
val pixel = argb[index]
r = pixel and 0xff0000 shr 16
g = pixel and 0xff00 shr 8
b = pixel and 0xff
Y = (66 * r + 129 * g + 25 * b + 128 shr 8) + 16
u = (-38 * r - 74 * g + 112 * b + 128 shr 8) + 128
v = (112 * r - 94 * g - 18 * b + 128 shr 8) + 128
yuv[yIndex++] = (if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte()
if (y % 2 == 0 && index % 2 == 0) {
yuv[uvIndex++] = (if (v < 0) 0 else if (v > 255) 255 else v).toByte()
yuv[uvIndex++] = (if (u < 0) 0 else if (u > 255) 255 else u).toByte()
}
++index
}
}
return yuv
}
private fun ceilIfUneven(n: Int) = if (n and 1 == 1) n + 1 else n
@@ -24,7 +24,7 @@ import com.google.zxing.ResultMetadataType
import com.google.zxing.ResultPointCallback
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.graphics.Mapping
import de.markusfisch.android.binaryeye.graphics.frameToView
import de.markusfisch.android.binaryeye.graphics.isPortrait
@@ -34,8 +34,6 @@ import de.markusfisch.android.binaryeye.widget.DetectorView
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.cameraview.widget.CameraView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@@ -64,8 +62,11 @@ class CameraActivity : AppCompatActivity() {
private var rotate = false
private var invert = false
private var flash = false
private var decoding = true
private var returnResult = false
private var frontFacing = false
private var bulkMode = false
private var ignoreNext: String? = null
private var fallbackBuffer: IntArray? = null
override fun onRequestPermissionsResult(
@@ -74,7 +75,7 @@ class CameraActivity : AppCompatActivity() {
grantResults: IntArray
) {
when (requestCode) {
REQUEST_CAMERA -> if (grantResults.isNotEmpty() &&
PERMISSION_CAMERA -> if (grantResults.isNotEmpty() &&
grantResults[0] != PackageManager.PERMISSION_GRANTED
) {
toast(R.string.no_camera_no_fun)
@@ -116,9 +117,7 @@ class CameraActivity : AppCompatActivity() {
initCameraView()
initZoomBar()
restoreZoom()
detectorView.updateRoi = { recreatePreprocessor = true }
detectorView.setPaddingFromWindowInsets()
initDetectorView()
if (intent?.action == Intent.ACTION_SEND &&
intent.type == "text/plain"
@@ -132,6 +131,7 @@ class CameraActivity : AppCompatActivity() {
resetPreProcessor()
fallbackBuffer = null
saveZoom()
saveCropHandlePos()
}
override fun onResume() {
@@ -139,7 +139,7 @@ class CameraActivity : AppCompatActivity() {
System.gc()
zxing.updateHints(prefs.tryHarder)
returnResult = "com.google.zxing.client.android.SCAN" == intent.action
if (hasCameraPermission(this, REQUEST_CAMERA)) {
if (hasCameraPermission(this)) {
openCamera()
}
}
@@ -178,17 +178,29 @@ class CameraActivity : AppCompatActivity() {
zoomBar.max = savedState.getInt(ZOOM_MAX)
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
frontFacing = savedState.getBoolean(FRONT_FACING)
bulkMode = savedState.getBoolean(BULK_MODE)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(ZOOM_MAX, zoomBar.max)
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
outState.putBoolean(FRONT_FACING, frontFacing)
outState.putBoolean(BULK_MODE, bulkMode)
super.onSaveInstanceState(outState)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// always give crop handle precedence over other controls
// because it can easily overlap and would then be inaccessible
if (detectorView.onTouchEvent(ev)) {
return true
}
return super.dispatchTouchEvent(ev)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_camera, menu)
menu.findItem(R.id.bulk_mode).isChecked = bulkMode
return true
}
@@ -218,6 +230,11 @@ class CameraActivity : AppCompatActivity() {
switchCamera()
true
}
R.id.bulk_mode -> {
bulkMode = bulkMode xor true
item.isChecked = bulkMode
true
}
R.id.preferences -> {
startActivity(MainActivity.getPreferencesIntent(this))
true
@@ -347,7 +364,8 @@ class CameraActivity : AppCompatActivity() {
val frameWidth = cameraView.frameWidth
val frameHeight = cameraView.frameHeight
val frameOrientation = cameraView.frameOrientation
var decoding = true
ignoreNext = null
decoding = true
camera.setPreviewCallback { frameData, _ ->
if (decoding) {
decodeFrame(
@@ -356,8 +374,10 @@ class CameraActivity : AppCompatActivity() {
frameHeight,
frameOrientation
)?.let { result ->
postResult(result)
decoding = false
if (result.text != ignoreNext) {
postResult(result)
decoding = false
}
}
}
}
@@ -387,6 +407,7 @@ class CameraActivity : AppCompatActivity() {
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
restoreZoom()
}
@Suppress("DEPRECATION")
@@ -418,6 +439,33 @@ class CameraActivity : AppCompatActivity() {
)
}
private fun initDetectorView() {
detectorView.onRoiChange = {
decoding = false
}
detectorView.onRoiChanged = {
decoding = true
recreatePreprocessor = true
}
detectorView.setPaddingFromWindowInsets()
restoreCropHandlePos()
}
private fun saveCropHandlePos() {
val pos = detectorView.getCropHandlePos()
prefs.cropHandleX = pos.x
prefs.cropHandleY = pos.y
prefs.cropHandleOrientation = detectorView.currentOrientation
}
private fun restoreCropHandlePos() {
detectorView.setCropHandlePos(
prefs.cropHandleX,
prefs.cropHandleY,
prefs.cropHandleOrientation
)
}
private fun updateFlashFab(unavailable: Boolean) {
if (unavailable) {
flashFab.setImageResource(R.drawable.ic_action_create)
@@ -591,37 +639,46 @@ class CameraActivity : AppCompatActivity() {
showResult(
this@CameraActivity,
result,
returnResult
returnResult,
bulkMode
)
if (bulkMode) {
ignoreNext = result.text
toast(result.text)
detectorView.postDelayed({
decoding = true
}, 500)
}
}
}
private fun getMapping() = if (rotate) rotatedMapping else nativeMapping
companion object {
private const val REQUEST_CAMERA = 1
private const val PICK_FILE_RESULT_CODE = 1
private const val ZOOM_MAX = "zoom_max"
private const val ZOOM_LEVEL = "zoom_level"
private const val FRONT_FACING = "front_facing"
private const val BULK_MODE = "bulk_mode"
}
}
fun showResult(
activity: Activity,
result: Result,
isResult: Boolean = false
isResult: Boolean = false,
bulkMode: Boolean = false
) {
val scan = Scan(result)
if (isResult) {
activity.setResult(Activity.RESULT_OK, getReturnIntent(result))
activity.finish()
} else {
if (prefs.useHistory) {
GlobalScope.launch {
db.insertScan(scan)
}
}
return
}
val scan = Scan(result)
if (prefs.useHistory) {
scan.id = db.insertScan(scan)
}
if (!bulkMode) {
activity.startActivity(
MainActivity.getDecodeIntent(activity, scan)
)
@@ -11,7 +11,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
@@ -25,6 +25,7 @@ import de.markusfisch.android.binaryeye.view.recordToolbarHeight
import de.markusfisch.android.binaryeye.widget.CropImageView
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.binaryeye.zxing.Zxing
import kotlinx.coroutines.*
class PickActivity : AppCompatActivity() {
private val zxing = Zxing()
@@ -68,6 +69,8 @@ class PickActivity : AppCompatActivity() {
return
}
var result: Result? = null
val scope = CoroutineScope(Dispatchers.IO)
val scannedRect = Rect()
fun scanWithinBounds() = crop(
bitmap,
@@ -75,31 +78,38 @@ class PickActivity : AppCompatActivity() {
cropImageView.imageRotation
)?.let {
scannedRect.set(0, 0, it.width, it.height)
zxing.decodePositiveNegative(it)
scope.launch(Dispatchers.IO) {
result = zxing.decodePositiveNegative(it)
result?.let {
withContext(Dispatchers.Main) {
if (!isFinishing) {
vibrator.vibrate()
}
cropImageView.updateResultPoints(
mapResult(
scannedRect.width(),
scannedRect.height(),
0,
cropImageView.getBoundsRect(),
it
)
)
}
}
}
}
cropImageView = findViewById(R.id.image) as CropImageView
cropImageView.setImageBitmap(bitmap)
cropImageView.onScan = {
scanWithinBounds()?.let {
if (!isFinishing) {
vibrator.vibrate()
}
mapResult(
scannedRect.width(),
scannedRect.height(),
0,
cropImageView.getBoundsRect(),
it
)
}
scanWithinBounds()
}
cropImageView.doOnApplyWindowInsets { v, insets ->
(v as CropImageView).windowInsets.set(insets)
}
findViewById(R.id.scan).setOnClickListener {
scanImage(scanWithinBounds())
scanImage(result)
}
}
@@ -8,7 +8,7 @@ import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.database.Database
class ScansAdapter(context: Context, cursor: Cursor) :
CursorAdapter(context, cursor, false) {
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.app
import android.app.Application
import android.support.v8.renderscript.RenderScript
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.database.Database
import de.markusfisch.android.binaryeye.preference.Preferences
val db = Database()
@@ -11,12 +11,11 @@ val prefs = Preferences()
class BinaryEyeApp : Application() {
override fun onCreate() {
super.onCreate()
db.open(this)
prefs.init(this)
if (prefs.forceCompat) {
RenderScript.forceCompat()
}
db.open(this)
}
}
@@ -6,41 +6,48 @@ import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
fun hasCameraPermission(activity: Activity, requestCode: Int = 1): Boolean {
return hasPermission(activity, Manifest.permission.CAMERA, requestCode)
const val PERMISSION_CAMERA = 1
fun hasCameraPermission(activity: Activity): Boolean {
return hasPermission(
activity,
Manifest.permission.CAMERA,
PERMISSION_CAMERA
)
}
fun hasWritePermission(activity: Activity, requestCode: Int = 2): Boolean {
const val PERMISSION_WRITE = 2
fun hasWritePermission(activity: Activity): Boolean {
return hasPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
requestCode
PERMISSION_WRITE
)
}
fun hasLocationPermission(activity: Activity, requestCode: Int = 3): Boolean {
const val PERMISSION_LOCATION = 3
fun hasLocationPermission(activity: Activity): Boolean {
return hasPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION,
requestCode
PERMISSION_LOCATION
)
}
fun hasPermission(
private fun hasPermission(
activity: Activity,
permission: String,
requestCode: Int
): Boolean {
return if (ContextCompat.checkSelfPermission(activity, permission) !=
PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
requestCode
)
false
} else {
true
}
) = if (ContextCompat.checkSelfPermission(
activity,
permission
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
requestCode
)
false
} else {
true
}
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.Context
import android.database.Cursor
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.ContentValues
import android.content.Context
@@ -1,16 +1,12 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.app.Activity
import android.os.Environment
import de.markusfisch.android.binaryeye.app.hasWritePermission
import de.markusfisch.android.binaryeye.app.writeExternalFile
import java.io.File
import java.io.FileInputStream
fun exportDatabase(activity: Activity, fileName: String): Boolean {
if (!hasWritePermission(activity)) {
return false
}
val dbFile = File(
Environment.getDataDirectory(),
"//data//${activity.packageName}//databases//${Database.FILE_NAME}"
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.Context
import android.database.Cursor
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.os.Parcel
import android.os.Parcelable
@@ -19,7 +19,7 @@ data class Scan(
val suggestedPrice: String?,
val upcEanExtension: String?,
val timestamp: String = getDateTime(),
val id: Long = 0L
var id: Long = 0L
) : Parcelable {
constructor(result: Result) : this(
result.text,
@@ -25,7 +25,12 @@ import java.io.OutputStream
import java.util.*
class BarcodeFragment : Fragment() {
private var barcode: Bitmap? = null
private enum class FileType {
PNG, SVG
}
private var barcodeBitmap: Bitmap? = null
private var barcodeSvg: String? = null
private var content: String = ""
private var format: BarcodeFormat? = null
@@ -53,7 +58,8 @@ class BarcodeFragment : Fragment() {
val format = args.getSerializable(FORMAT) as BarcodeFormat? ?: return view
val size = args.getInt(SIZE)
try {
barcode = Zxing.encodeAsBitmap(content, format, size, size)
barcodeBitmap = Zxing.encodeAsBitmap(content, format, size, size)
barcodeSvg = Zxing.encodeAsSvg(content, format, size, size)
} catch (e: Exception) {
var message = e.message
if (message == null || message.isEmpty()) {
@@ -71,14 +77,14 @@ class BarcodeFragment : Fragment() {
val imageView = view.findViewById<ConfinedScalingImageView>(
R.id.barcode
)
imageView.setImageBitmap(barcode)
imageView.setImageBitmap(barcodeBitmap)
imageView.post {
// make sure to invoke this after ScalingImageView.onLayout()
imageView.minWidth /= 2f
}
view.findViewById<View>(R.id.share).setOnClickListener {
val bitmap = barcode
val bitmap = barcodeBitmap
bitmap?.let {
share(bitmap)
}
@@ -98,8 +104,12 @@ class BarcodeFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.save -> {
askForFileNameAndSave()
R.id.export_svg -> {
askForFileNameAndSave(FileType.SVG)
true
}
R.id.export_png -> {
askForFileNameAndSave(FileType.PNG)
true
}
else -> super.onOptionsItemSelected(item)
@@ -108,23 +118,33 @@ class BarcodeFragment : Fragment() {
// dialogs do not have a parent view
@SuppressLint("InflateParams")
private fun askForFileNameAndSave() {
private fun askForFileNameAndSave(fileType: FileType) {
val ac = activity ?: return
if (!hasWritePermission(ac)) {
return
}
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById<EditText>(R.id.file_name)
editText.setText(encodeFileName("${format.toString()}_$content"))
AlertDialog.Builder(ac)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
val bitmap = barcode
bitmap?.let {
saveAsFile(
bitmap,
addSuffixIfNotGiven(
editText.text.toString(),
".png"
)
)
val fileName = editText.text.toString()
when (fileType) {
FileType.PNG -> saveAs(
addSuffixIfNotGiven(fileName, ".png"),
"image/png"
) {
barcodeBitmap?.saveAsPng(it)
}
FileType.SVG -> saveAs(
addSuffixIfNotGiven(fileName, ".svg"),
"image/svg+xmg"
) { outputStream ->
barcodeSvg?.let {
outputStream.write(it.toByteArray())
}
}
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
@@ -132,15 +152,14 @@ class BarcodeFragment : Fragment() {
.show()
}
private fun saveAsFile(bitmap: Bitmap, fileName: String) {
private fun saveAs(
fileName: String,
mimeType: String,
write: (outputStream: OutputStream) -> Unit
) {
val ac = activity ?: return
if (!hasWritePermission(ac)) {
return
}
GlobalScope.launch {
val message = writeExternalFile(ac, fileName, "image/png") {
bitmap.saveAsPng(it)
}.toSaveResult()
val message = writeExternalFile(ac, fileName, mimeType, write).toSaveResult()
GlobalScope.launch(Main) {
ac.toast(message)
}
@@ -15,7 +15,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.toast
import kotlinx.coroutines.CoroutineScope
@@ -31,13 +31,14 @@ class DecodeFragment : Fragment() {
private lateinit var format: String
private lateinit var fab: FloatingActionButton
private var action = ActionRegistry.DEFAULT_ACTION
private var isBinary = false
private val parentJob = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
private val content: String
get() = contentView.text.toString()
private val parentJob = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
private var action = ActionRegistry.DEFAULT_ACTION
private var isBinary = false
private var id = 0L
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@@ -59,6 +60,7 @@ class DecodeFragment : Fragment() {
val scan = arguments?.getParcelable(SCAN) as Scan?
?: throw IllegalArgumentException("DecodeFragment needs a Scan")
id = scan.id
val inputContent = scan.content
isBinary = hasNonPrintableCharacters(
@@ -196,6 +198,9 @@ class DecodeFragment : Fragment() {
menu.findItem(R.id.copy_to_clipboard).isVisible = false
menu.findItem(R.id.create).isVisible = false
}
if (id > 0L) {
menu.findItem(R.id.remove).isVisible = true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -215,6 +220,11 @@ class DecodeFragment : Fragment() {
)
true
}
R.id.remove -> {
db.removeScan(id)
backOrFinish()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -226,6 +236,15 @@ class DecodeFragment : Fragment() {
}
}
private fun backOrFinish() {
val fm = fragmentManager
if (fm != null && fm.backStackEntryCount > 0) {
fm.popBackStack()
} else {
activity?.finish()
}
}
private fun executeAction(content: ByteArray) {
val ac = activity ?: return
if (content.isNotEmpty()) {
@@ -21,10 +21,10 @@ import android.widget.ListView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.data.exportCsv
import de.markusfisch.android.binaryeye.data.exportDatabase
import de.markusfisch.android.binaryeye.data.exportJson
import de.markusfisch.android.binaryeye.database.Database
import de.markusfisch.android.binaryeye.database.exportCsv
import de.markusfisch.android.binaryeye.database.exportDatabase
import de.markusfisch.android.binaryeye.database.exportJson
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.view.useVisibility
import de.markusfisch.android.binaryeye.widget.toast
@@ -12,10 +12,7 @@ class Dots(context: Context) {
private val radius = 8f * context.resources.displayMetrics.density
init {
paint.color = ContextCompat.getColor(
context,
R.color.dot
)
paint.color = ContextCompat.getColor(context, R.color.dot)
paint.style = Paint.Style.FILL
}
@@ -7,6 +7,21 @@ import android.preference.PreferenceManager
class Preferences {
lateinit var preferences: SharedPreferences
var cropHandleX = -1
set(value) {
apply(CROP_HANDLE_X, value)
field = value
}
var cropHandleY = -1
set(value) {
apply(CROP_HANDLE_Y, value)
field = value
}
var cropHandleOrientation = 0
set(value) {
apply(CROP_HANDLE_ORIENTATION, value)
field = value
}
var showCropHandle = true
set(value) {
apply(SHOW_CROP_HANDLE, value)
@@ -85,6 +100,12 @@ class Preferences {
}
fun update() {
cropHandleX = preferences.getInt(CROP_HANDLE_X, cropHandleX)
cropHandleY = preferences.getInt(CROP_HANDLE_Y, cropHandleY)
cropHandleOrientation = preferences.getInt(
CROP_HANDLE_ORIENTATION,
cropHandleOrientation
)
showCropHandle = preferences.getBoolean(SHOW_CROP_HANDLE, showCropHandle)
zoomBySwiping = preferences.getBoolean(ZOOM_BY_SWIPING, zoomBySwiping)
autoRotate = preferences.getBoolean(AUTO_ROTATE, autoRotate)
@@ -131,6 +152,9 @@ class Preferences {
}
companion object {
const val CROP_HANDLE_X = "crop_handle_x"
const val CROP_HANDLE_Y = "crop_handle_y"
const val CROP_HANDLE_ORIENTATION = "crop_handle_orientation"
const val SHOW_CROP_HANDLE = "show_crop_handle"
const val ZOOM_BY_SWIPING = "zoom_by_swiping"
const val AUTO_ROTATE = "auto_rotate"
@@ -3,7 +3,7 @@ package de.markusfisch.android.binaryeye.rs
import android.content.Context
import android.graphics.Rect
import android.support.v8.renderscript.*
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import kotlin.math.max
import kotlin.math.roundToInt
private const val SCALE_FACTOR = .75f
@@ -19,7 +19,7 @@ class Preprocessor(
private val rs = RenderScript.create(context)
private val resizeScript = ScriptIntrinsicResize.create(rs)
private val rotatorScript = ScriptC_rotator(rs)
private val rotateScript = ScriptC_rotate(rs)
private var yuvType: Type? = null
private var yuvAlloc: Allocation? = null
@@ -65,6 +65,10 @@ class Preprocessor(
// make sure the dimensions are always a multiple of 4
outWidth -= outWidth % 4
outHeight -= outHeight % 4
// make sure the dimensions are always greater than 4
outWidth = max(4, outWidth)
outHeight = max(4, outHeight)
} else {
outWidth = (width * SCALE_FACTOR).roundToInt()
outHeight = (height * SCALE_FACTOR).roundToInt()
@@ -113,7 +117,7 @@ class Preprocessor(
rotatedAlloc?.destroy()
rotatedAlloc = null
resizeScript.destroy()
rotatorScript.destroy()
rotateScript.destroy()
rs.destroy()
}
@@ -125,10 +129,10 @@ class Preprocessor(
fun resizeAndRotate(frame: ByteArray) {
resize(frame)
val t = resizedType ?: return
rotatorScript._inImage = resizedAlloc
rotatorScript._inWidth = t.x
rotatorScript._inHeight = t.y
rotatorScript.forEach_rotate90(
rotateScript._inImage = resizedAlloc
rotateScript._inWidth = t.x
rotateScript._inHeight = t.y
rotateScript.forEach_rotate90(
rotatedAlloc, // ignored in kernel, just to satisfy forEach
rotatedAlloc
)
@@ -14,18 +14,13 @@ class CropImageView(context: Context, attr: AttributeSet) :
ConfinedScalingImageView(context, attr) {
val windowInsets = Rect()
var onScan: (() -> List<Point>?)? = null
var onScan: (() -> Unit)? = null
private val dots = Dots(context)
private val boundsPaint = context.getDashedBorderPaint()
private val lastMappedRect = RectF()
private val padding: Int = (24f * context.resources.displayMetrics.density).roundToInt()
private val onScanRunnable = Runnable {
onScan?.invoke()?.let {
resultPoints = it
invalidate()
}
}
private val onScanRunnable = Runnable { onScan?.invoke() }
private var resultPoints: List<Point>? = null
@@ -33,6 +28,11 @@ class CropImageView(context: Context, attr: AttributeSet) :
scaleType = ScaleType.CENTER_CROP
}
fun updateResultPoints(points: List<Point>) {
resultPoints = points
invalidate()
}
override fun onLayout(
changed: Boolean,
left: Int,
@@ -15,14 +15,18 @@ import de.markusfisch.android.binaryeye.graphics.Dots
import de.markusfisch.android.binaryeye.graphics.getBitmapFromDrawable
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class DetectorView : View {
val currentOrientation = resources.configuration.orientation
val roi = Rect()
var updateRoi: (() -> Unit)? = null
var onRoiChange: (() -> Unit)? = null
var onRoiChanged: (() -> Unit)? = null
private val orientation = resources.configuration.orientation
private val dots = Dots(context)
private val invalidateRunnable: Runnable = Runnable {
marks = null
@@ -34,8 +38,8 @@ class DetectorView : View {
)
private val handleXRadius = handleBitmap.width / 2
private val handleYRadius = handleBitmap.height / 2
private val handleHome = Point()
private val handlePos = Point(-1, -1)
private val handleHome = Point()
private val center = Point()
private val touchDown = Point()
private val distToFull: Int
@@ -45,9 +49,11 @@ class DetectorView : View {
private val padding: Int
private var marks: List<Point>? = null
private var orientation = resources.configuration.orientation
private var handleGrabbed = false
private var handleMoved = false
private var handleActive = false
private var minY = 0
private var maxY = 0
private var minDist = 0
private var shadeColor = 0
init {
@@ -67,6 +73,23 @@ class DetectorView : View {
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
fun setCropHandlePos(x: Int, y: Int, orientation: Int) {
if (orientation == currentOrientation) {
handlePos.set(x, y)
} else {
handlePos.set(y, x)
}
if (x > -1) {
handleActive = true
}
}
fun getCropHandlePos() = if (handleActive) {
handlePos
} else {
Point(-1, -1)
}
fun mark(points: List<Point>) {
marks = points
invalidate()
@@ -75,27 +98,23 @@ class DetectorView : View {
}
override fun onSaveInstanceState(): Parcelable? {
if (!handleMoved) {
if (!handleActive) {
return super.onSaveInstanceState()
}
return SavedState(super.onSaveInstanceState()).apply {
savedHandlePos.set(handlePos)
savedOrientation = orientation
savedHandlePos.set(getCropHandlePos())
savedOrientation = currentOrientation
}
}
override fun onRestoreInstanceState(state: Parcelable) {
super.onRestoreInstanceState(
if (state is SavedState) {
if (state.savedOrientation == orientation) {
handlePos.set(state.savedHandlePos)
} else {
handlePos.set(
state.savedHandlePos.y,
state.savedHandlePos.x
)
}
handleMoved = true
setCropHandlePos(
state.savedHandlePos.x,
state.savedHandlePos.y,
state.savedOrientation
)
state.superState
} else {
state
@@ -114,6 +133,9 @@ class DetectorView : View {
touchDown.set(x, y)
handleGrabbed = abs(x - handlePos.x) < handleXRadius &&
abs(y - handlePos.y) < handleYRadius
if (handleGrabbed) {
onRoiChange?.invoke()
}
handleGrabbed
} else {
false
@@ -123,8 +145,9 @@ class DetectorView : View {
if (handleGrabbed) {
handlePos.set(x, y)
if (distSq(handlePos, touchDown) > minMoveThresholdSq) {
handleMoved = true
handleActive = true
}
updateClipRect()
invalidate()
true
} else {
@@ -140,17 +163,19 @@ class DetectorView : View {
}
MotionEvent.ACTION_UP -> {
if (handleGrabbed) {
if (!handleMoved) {
if (!handleActive) {
val mn = min(center.x, center.y) * .8f
handlePos.set(
(center.x * 1.5f).roundToInt(),
(center.y * 1.25f).roundToInt()
(center.x + mn).roundToInt(),
(center.y + mn).roundToInt()
)
handleMoved = true
handleActive = true
invalidate()
} else {
snap(x, y)
}
updateRoi?.invoke()
updateClipRect()
onRoiChanged?.invoke()
handleGrabbed = false
}
false
@@ -163,32 +188,44 @@ class DetectorView : View {
if (abs(x - center.x) < distToFull ||
abs(y - center.y) < distToFull
) {
handlePos.set(handleHome)
handleMoved = false
reset()
invalidate()
}
}
private fun reset() {
handlePos.set(handleHome)
handleActive = false
roi.set(0, 0, 0, 0)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (!changed) {
return
}
val width = right - left
val height = bottom - top
center.set(
left + (width / 2),
top + (height / 2)
)
minY = padding * 2
maxY = height - minY
handleHome.set(
width - handleXRadius - paddingRight - padding,
height - handleYRadius - paddingBottom - fabHeight
)
if (handlePos.x < 0) {
handlePos.set(handleHome)
if (handleActive) {
updateClipRect()
} else {
reset()
}
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
if (handleMoved) {
if (handleActive) {
drawClip(canvas)
}
marks?.let {
@@ -205,7 +242,6 @@ class DetectorView : View {
}
private fun drawClip(canvas: Canvas) {
val minDist = updateClipRect()
if (minDist < 1) {
return
}
@@ -230,13 +266,14 @@ class DetectorView : View {
}
}
private fun updateClipRect(): Int {
private fun updateClipRect() {
clampHandlePos()
val dx = abs(handlePos.x - center.x)
val dy = abs(handlePos.y - center.y)
val d = min(dx, dy)
minDist = min(dx, dy)
shadeColor = (min(
1f,
d.toFloat() / distToFull.toFloat()
minDist.toFloat() / distToFull.toFloat()
) * 128f).toInt() shl 24
roi.set(
center.x - dx,
@@ -244,7 +281,11 @@ class DetectorView : View {
center.x + dx,
center.y + dy
)
return d
}
private fun clampHandlePos() {
handlePos.x = min(center.x * 2, max(0, handlePos.x))
handlePos.y = min(maxY, max(minY, handlePos.y))
}
internal class SavedState : BaseSavedState {
@@ -156,5 +156,42 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
return bitmap
}
fun encodeAsSvg(
text: String,
format: BarcodeFormat,
width: Int,
height: Int
): String {
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val result = MultiFormatWriter().encode(
text,
format,
0,
0,
hints
)
val sb = StringBuilder()
sb.append("<svg width=\"$width\" height=\"$height\"")
sb.append(" viewBox=\"0 0 $width $height\"")
sb.append(" xmlns=\"http://www.w3.org/2000/svg\">\n")
val w = result.width
val h = result.height
val xf = width.toFloat() / w
val yf = height.toFloat() / h
for (y in 0 until h) {
for (x in 0 until w) {
if (result.get(x, y)) {
val ox = x * xf
val oy = y * yf
sb.append("<rect x=\"$ox\" y=\"$oy\"")
sb.append(" width=\"$xf\" height=\"$yf\"/>\n")
}
}
}
sb.append("</svg>\n")
return sb.toString()
}
}
}
+11 -25
View File
@@ -1,26 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M28,32m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0">
<aapt:attr name="android:fillColor">
<gradient
android:gradientRadius="24"
android:centerX="28"
android:centerY="32"
android:type="radial">
<item android:offset="0" android:color="#FF000000"/>
<item android:offset="1" android:color="#00000000"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M28,28m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:fillColor="#B6D46F"/>
<path
android:pathData="M33,31L35,31L35,23C35,21.9 34.1,21 33,21L25,21L25,23L33,23L33,31ZM23,33L23,17L21,17L21,21L17,21L17,23L21,23L21,33C21,34.1 21.9,35 23,35L33,35L33,39L35,39L35,35L39,35L39,33L23,33Z"
android:fillColor="#FFFFFF"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="56dp" android:height="56dp" android:viewportWidth="56" android:viewportHeight="56">
<path android:pathData="M4 32a24 24 0 1 1 48 0 24 24 0 1 1-48 0">
<aapt:attr name="android:fillColor">
<gradient android:gradientRadius="24" android:centerX="28" android:centerY="32" android:type="radial">
<item android:offset="0" android:color="#FF000000"/>
<item android:offset="1" android:color="#00000000"/>
</gradient>
</aapt:attr>
</path>
<path android:pathData="M4 28a24 24 0 1 1 48 0 24 24 0 1 1-48 0" android:fillColor="#B6D46F"/>
<path android:pathData="M33 31h2v-8c0-1.1-0.9-2-2-2h-8v2h8v8zm-10 2V17h-2v4h-4v2h4v10c0 1.1 0.9 2 2 2h10v4h2v-4h4v-2H23z" android:fillColor="#FFFFFF"/>
</vector>
@@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M14 10H2v2h12v-2zm0-4H2v2h12V6zm4 8v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2 16h8v-2H2v2z"/>
</vector>
@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M4.413 15.067h-0.88v1.93h-1.37v-5.69h2.27c0.433 0 0.817 0.08 1.15 0.24 0.34 0.16 0.6 0.39 0.78 0.69 0.187 0.293 0.28 0.627 0.28 1 0 0.553-0.2 0.997-0.6 1.33-0.4 0.333-0.943 0.5-1.63 0.5zm-0.88-2.7v1.64h0.9c0.267 0 0.47-0.067 0.61-0.2s0.21-0.32 0.21-0.56c0-0.267-0.073-0.48-0.22-0.64-0.14-0.16-0.337-0.24-0.59-0.24h-0.91zm8.587-1.06v5.69h-1.36l-2.02-3.54v3.54H7.37v-5.69h1.37l2.01 3.54v-3.54h1.37zm5.527 2.65v2.34c-0.213 0.233-0.523 0.42-0.93 0.56-0.407 0.147-0.853 0.22-1.34 0.22-0.74 0-1.333-0.23-1.78-0.69-0.447-0.453-0.687-1.087-0.72-1.9v-0.49c0-0.56 0.1-1.05 0.3-1.47 0.193-0.413 0.477-0.733 0.85-0.96 0.367-0.227 0.793-0.34 1.28-0.34 0.707 0 1.257 0.163 1.65 0.49 0.387 0.32 0.613 0.803 0.68 1.45h-1.32c-0.047-0.32-0.147-0.547-0.3-0.68-0.16-0.133-0.383-0.2-0.67-0.2-0.34 0-0.603 0.143-0.79 0.43-0.193 0.293-0.29 0.71-0.29 1.25v0.35c0 0.567 0.097 0.993 0.29 1.28 0.193 0.28 0.5 0.42 0.92 0.42 0.353 0 0.617-0.077 0.79-0.23v-0.89h-0.95v-0.94h2.33zm4.733-6.991L18.481 6.96l1.463 1.464-2.065 2.066 0.975 0.975L20.921 9.4l1.463 1.464-0.005-3.898z"/>
</vector>
@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M5.45 15.487c0-0.2-0.07-0.357-0.21-0.47-0.14-0.113-0.39-0.23-0.75-0.35-0.353-0.12-0.643-0.237-0.87-0.35-0.74-0.36-1.11-0.857-1.11-1.49 0-0.313 0.093-0.59 0.28-0.83 0.18-0.24 0.44-0.427 0.78-0.56 0.333-0.14 0.71-0.21 1.13-0.21 0.407 0 0.773 0.073 1.1 0.22 0.327 0.147 0.577 0.357 0.75 0.63 0.18 0.267 0.27 0.573 0.27 0.92H5.46c0-0.233-0.073-0.413-0.22-0.54-0.14-0.127-0.33-0.19-0.57-0.19-0.247 0-0.44 0.053-0.58 0.16-0.14 0.107-0.21 0.243-0.21 0.41 0 0.147 0.077 0.28 0.23 0.4 0.153 0.12 0.427 0.243 0.82 0.37 0.393 0.127 0.717 0.263 0.97 0.41 0.613 0.353 0.92 0.84 0.92 1.46 0 0.5-0.187 0.89-0.56 1.17-0.373 0.287-0.887 0.43-1.54 0.43-0.46 0-0.877-0.083-1.25-0.25-0.373-0.167-0.657-0.393-0.85-0.68-0.187-0.287-0.28-0.617-0.28-0.99h1.38c0 0.3 0.077 0.523 0.23 0.67 0.16 0.147 0.417 0.22 0.77 0.22 0.227 0 0.407-0.05 0.54-0.15 0.127-0.1 0.19-0.237 0.19-0.41zm3.158-4.18l1.13 4.14 1.13-4.14h1.53l-1.91 5.69h-1.5l-1.9-5.69h1.52zm8.814 2.65v2.34c-0.213 0.233-0.523 0.42-0.93 0.56-0.407 0.147-0.853 0.22-1.34 0.22-0.74 0-1.333-0.23-1.78-0.69-0.447-0.453-0.687-1.087-0.72-1.9v-0.49c0-0.56 0.1-1.05 0.3-1.47 0.193-0.413 0.477-0.733 0.85-0.96 0.367-0.227 0.793-0.34 1.28-0.34 0.707 0 1.257 0.163 1.65 0.49 0.387 0.32 0.613 0.803 0.68 1.45h-1.32c-0.047-0.32-0.147-0.547-0.3-0.68-0.16-0.133-0.383-0.2-0.67-0.2-0.34 0-0.603 0.143-0.79 0.43-0.193 0.293-0.29 0.71-0.29 1.25v0.35c0 0.567 0.097 0.993 0.29 1.28 0.193 0.28 0.5 0.42 0.92 0.42 0.353 0 0.617-0.077 0.79-0.23v-0.89h-0.95v-0.94h2.33zm4.164-7.051L17.69 6.9l1.463 1.464-2.065 2.066 0.975 0.975 2.065-2.066 1.464 1.464-0.005-3.898z"/>
</vector>
@@ -21,6 +21,12 @@
android:title="@string/switch_camera"
android:icon="@drawable/ic_action_switch_camera"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/bulk_mode"
android:title="@string/bulk_mode"
android:icon="@drawable/ic_action_bulk_mode"
android:checkable="true"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/preferences"
android:title="@string/preferences"
+8 -3
View File
@@ -2,8 +2,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/save"
android:title="@string/export_to_file"
android:icon="@drawable/ic_action_save"
android:id="@+id/export_svg"
android:title="@string/export_svg"
android:icon="@drawable/ic_action_save_svg"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/export_png"
android:title="@string/export_png"
android:icon="@drawable/ic_action_save_png"
material:showAsAction="ifRoom"/>
</menu>
+9 -3
View File
@@ -1,19 +1,25 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/remove"
android:title="@string/remove_scan"
android:icon="@drawable/ic_action_remove"
android:visible="false"
material:showAsAction="always"/>
<item
android:id="@+id/copy_to_clipboard"
android:title="@string/copy_to_clipboard"
android:icon="@drawable/ic_action_copy"
material:showAsAction="ifRoom"/>
material:showAsAction="always"/>
<item
android:id="@+id/share"
android:title="@string/share"
android:icon="@drawable/ic_action_share"
material:showAsAction="ifRoom"/>
material:showAsAction="always"/>
<item
android:id="@+id/create"
android:title="@string/compose_barcode"
android:icon="@drawable/ic_action_create"
material:showAsAction="ifRoom"/>
material:showAsAction="always"/>
</menu>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Barcode ansehen</string>
<string name="info">Info</string>
<string name="switch_camera">Kamera wechseln</string>
<string name="bulk_mode">Fortlaufend Scannen</string>
<string name="history">Gespeicherte Codes</string>
<string name="preferences">Einstellungen</string>
<string name="show_crop_handle">Bereich verkleinern anzeigen</string>
@@ -83,6 +84,8 @@
<string name="search_web">Suche im Internet</string>
<string name="search_scan">Code suchen</string>
<string name="export_to_file">In Datei exportieren</string>
<string name="export_png">Als PNG exportieren</string>
<string name="export_svg">Als SVG exportieren</string>
<string name="export_as">Exportieren als?</string>
<string name="export_csv_comma">CSV mit Kommas</string>
<string name="export_csv_semicolon">CSV mit Semikolons</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Ver código de barras</string>
<string name="info">Información</string>
<string name="switch_camera">Cambiar cámara</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Historial</string>
<string name="preferences">Configuración</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Buscar en la web</string>
<string name="search_scan">Buscar escaneos</string>
<string name="export_to_file">Exportar a archivo</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">¿Dónde desea exportar?</string>
<string name="export_csv_comma">CSV con comas</string>
<string name="export_csv_semicolon">CSV con punto y coma</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Voir le code barre</string>
<string name="info">Info</string>
<string name="switch_camera">Changer de camera</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Historique</string>
<string name="preferences">Preferences</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Search the web</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Export to file</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Vonalkód megtekintése</string>
<string name="info">Információ</string>
<string name="switch_camera">Kamera átkapcsolása</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Előzmények</string>
<string name="preferences">Beállítások</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Keresés a weben</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Exportálás fájlba</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+3 -1
View File
@@ -8,7 +8,6 @@
<string name="binary_data">(data biner)</string>
<plurals name="barcode_info">
<item quantity="other">%1$s, %2$d karakter</item>
</plurals>
<string name="error_correction_level">Level koreksi kesalahan</string>
<string name="issue_number">Nomor pemindaian</string>
@@ -36,6 +35,7 @@
<string name="view_barcode">Lihat barcode</string>
<string name="info">Info</string>
<string name="switch_camera">Ganti kamera</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Riwayat</string>
<string name="preferences">Preferensi</string>
<string name="show_crop_handle">Tampilkan pembatas pemotongan</string>
@@ -83,6 +83,8 @@
<string name="search_web">Cari secara daring</string>
<string name="search_scan">Cari pindaian</string>
<string name="export_to_file">Ekspor ke berkas</string>
<string name="export_png">Ekspor sebagai PNG</string>
<string name="export_svg">Ekspor sebagai SVG</string>
<string name="export_as">Ekspor sebagai?</string>
<string name="export_csv_comma">CSV dengan koma</string>
<string name="export_csv_semicolon">CSV dengan titik koma</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Vedi codice a barre</string>
<string name="info">Info</string>
<string name="switch_camera">Cambia fotocamera</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Cronologia</string>
<string name="preferences">Preferenze</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Cerca nel web</string>
<string name="search_scan">Cerca scansione</string>
<string name="export_to_file">Esporta su file</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Esportare come?</string>
<string name="export_csv_comma">CSV con virgole</string>
<string name="export_csv_semicolon">CSV con punti e virgola</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">Barcode bekijken</string>
<string name="info">Informatie</string>
<string name="switch_camera">Camera wisselen</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Geschiedenis</string>
<string name="preferences">Voorkeuren</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -79,6 +80,8 @@
<string name="search_web">Het net doorzoeken</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Naar bestand exporteren</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+97
View File
@@ -0,0 +1,97 @@
<resources>
<string name="open_url">Otwórz url</string>
<string name="content">Zawartość</string>
<string name="copied_to_clipboard">Skopiowano do schowka</string>
<string name="copy_to_clipboard">Skopiuj do schowka</string>
<string name="info">Informacje</string>
<string name="vcard_add">Dodaj do kontaktów</string>
<string name="vevent_add">Dodaj do kalendarza</string>
<string name="shortcut_preferences">Ustawienia</string>
<string name="share">Udostępnij</string>
<string name="error_saving_file">Nie można zapisać pliku</string>
<string name="clear_history">Wyczyść historię</string>
<string name="compose_barcode">Utwórz kod kreskowy</string>
<string name="history">Historia</string>
<string name="format">Format</string>
<string name="export_to_file">Eksportuj do pliku</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="file_name">Nazwa pliku</string>
<string name="export_json">Eksportuj jako JSON</string>
<string name="pick_search_engine">Wybierz wyszukiwarkę</string>
<string name="pick_file">Wybierz plik obrazu</string>
<string name="pick_code_to_scan">Wybierz kod do zeskanowania</string>
<string name="sms_send">Wyślij SMS</string>
<string name="mail_send">Wyślij e-mail</string>
<string name="saved_in_downloads">Zapisano w pobranych</string>
<string name="separator_ruler">Linijka</string>
<string name="scan_code">Skanuj kod</string>
<string name="error_correction_level">Poziom korekcji błędów</string>
<string name="export_csv_comma">CSV z przecinkami</string>
<string name="export_csv_semicolon">CSV z średnikami</string>
<string name="shortcut_encode">Stwórz kod kreskowy</string>
<string name="error_file_exists">Plik już istnieje</string>
<string name="no_barcode_found">Nie znaleziono kodu kreskowego</string>
<string name="no_camera_no_fun">Ta aplikacja nie ma żadnego sensu bez dostępu do kamery. Do widzenia.</string>
<string name="tel_dial">Wybierz numer</string>
<string name="connect_to_wifi">Połącz z Wi-Fi</string>
<string name="vcard_failed">Nie można dodać do kontaktów</string>
<string name="tel_error">Nie można wybrać numeru</string>
<string name="sms_error">Nie można wysłać SMS-a</string>
<string name="mail_error">Nie można wysłać e-maila</string>
<string name="rotate_image_cw">Obróć obraz zgodnie z ruchem wskazówek zegara</string>
<string name="edit_scan">Edytuj etykietę</string>
<string name="enter_name_hint">Nazwa skanu</string>
<string name="camera_error">Nie można uzyskać dostępu do kamery, spróbuj ponownie</string>
<string name="copy_scan">Skopiuj skan</string>
<string name="vevent_failed">Nie można dodać do kalendarza</string>
<string name="wifi_config_failed">Nie można skonfigurować Wi-Fi</string>
<string name="size">Rozmiar w pikselach</string>
<string name="export_as">Eksportuj jako?</string>
<string name="really_remove_all_scans">Czy naprawdę usunąć wszystkie skany?</string>
<string name="really_remove_scan">Czy naprawdę usunąć skan?</string>
<string name="really_remove_selected_scans">Czy naprawdę usunąć wybrane skany?</string>
<string name="binary_data">(dane binarne)</string>
<string name="show_hex_dump">Pokazuj zrzut szesnastkowy</string>
<string name="show_meta_data">Pokazuj metadane</string>
<string name="switch_camera">Przełącz aparat</string>
<string name="bulk_mode">Scan continuously</string>
<string name="toggle_flash">Przełącz lampę błyskową</string>
<string name="wifi_added">Wi-Fi dodane</string>
<string name="zoom_by_swiping">Przybliżaj kamerę, przesuwając w górę/dół</string>
<string name="view_barcode">Wyświetl kod kreskowy</string>
<string name="orientation">Orientacja</string>
<string name="error_encoding_barcode">Nie można utworzyć kodu kreskowego</string>
<string name="otpauth_add">Dodaj 2FA</string>
<string name="other_meta_data">Metadane</string>
<string name="remove_scan">Usuń skan</string>
<string name="separator_line_break">Przerwanie linii</string>
<string name="shortcut_decode">Zeskanuj kod kreskowy</string>
<string name="use_history">Zapisuj historię skanowania</string>
<string name="vibrate">Wibruj po wykryciu</string>
<string name="open_immediately">Pomiń kontrolę i natychmiast otwórz zawartość</string>
<string name="show_crop_handle">Pokazuj ogranicznik przycinania</string>
<string name="search_web">Szukaj w sieci</string>
<string name="search_scan">Szukaj skanu</string>
<string name="export_database">Eksportuj do bazy danych SQLite</string>
<string name="ignore_consecutive_duplicates">Nie zapisuj kolejnych duplikatów</string>
<string name="open_with_url">Otwieraj nieznane dane za pomocą adresu URL</string>
<string name="copied_password_to_clipboard">Hasło skopiowane do schowka</string>
<string name="pdf417_extra_metadata">Metadane PDF417</string>
<string name="suggested_price">Sugerowana cena</string>
<string name="preferences">Ustawienia</string>
<string name="decode_barcode">Dekoduj kod kreskowy</string>
<string name="upc_ean_extension">Dodatek UPC EAN</string>
<string name="possible_country">Możliwy kraj produkcji</string>
<string name="encode">ZAKODUJ</string>
<string name="error_no_content">Brak zawartości</string>
<string name="try_harder">Zoptymalizuj czytnik pod kątem dokładności, a nie prędkości</string>
<string name="cannot_resolve_action">Żadna aplikacja nie może tego otworzyć</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Wprowadź zawartość tutaj</string>
<string name="pick_list_separator">Jak oddzielić elementy listy?</string>
<string name="enter_name">Wprowadź nazwę opisującą skan</string>
<string name="auto_rotate">Rozpoznawaj kody kreskowe 1D pionowo</string>
<string name="save_as_file_name">Jak zapisać plik w pobranych?</string>
<string name="issue_number">Numer wydania</string>
</resources>
@@ -36,6 +36,7 @@
<string name="view_barcode">Ver código de barras</string>
<string name="info">Informações</string>
<string name="switch_camera">Trocar câmera</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">Histórico</string>
<string name="preferences">Preferências</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Search the web</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Export to file</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+3 -2
View File
@@ -7,9 +7,7 @@
<string name="content">Текст</string>
<string name="binary_data">(двоичные данные)</string>
<plurals name="barcode_info">
<item quantity="zero">%2$d символов, %1$s</item>
<item quantity="one">%2$d символ, %1$s</item>
<item quantity="two">%2$d символа, %1$s</item>
<item quantity="few">%2$d символа, %1$s</item>
<item quantity="many">%2$d символов, %1$s</item>
<item quantity="other">%2$d символов, %1$s</item>
@@ -40,6 +38,7 @@
<string name="view_barcode">Просмотр штрих-кода</string>
<string name="info">Информация</string>
<string name="switch_camera">Переключить камеру</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">История</string>
<string name="preferences">Настройки</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -87,6 +86,8 @@
<string name="search_web">Искать в сети</string>
<string name="search_scan">Поиск сканирования</string>
<string name="export_to_file">Экспортировать в файл</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Формат экспортируемого файла</string>
<string name="export_csv_comma">CSV (разделитель - запятая)</string>
<string name="export_csv_semicolon">CSV (разделитель - точка с запятой)</string>
@@ -35,6 +35,7 @@
<string name="view_barcode">查看条形码</string>
<string name="info">信息</string>
<string name="switch_camera">切换摄像头</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">历史</string>
<string name="preferences">首选项</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -82,6 +83,8 @@
<string name="search_web">在互联网中搜索</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">导出为CSV文件</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+3
View File
@@ -35,6 +35,7 @@
<string name="view_barcode">檢視條碼</string>
<string name="info">資訊</string>
<string name="switch_camera">切換相機</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">歷史</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
@@ -82,6 +83,8 @@
<string name="search_web">Search the web</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Export to file</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
+3
View File
@@ -36,6 +36,7 @@
<string name="view_barcode">View barcode</string>
<string name="info">Info</string>
<string name="switch_camera">Switch camera</string>
<string name="bulk_mode">Scan continuously</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="show_crop_handle">Show cropping limiter</string>
@@ -83,6 +84,8 @@
<string name="search_web">Search the web</string>
<string name="search_scan">Search scan</string>
<string name="export_to_file">Export to file</string>
<string name="export_png">Export as PNG</string>
<string name="export_svg">Export as SVG</string>
<string name="export_as">Export as?</string>
<string name="export_csv_comma">CSV with commas</string>
<string name="export_csv_semicolon">CSV with semicolons</string>
@@ -1,17 +1,17 @@
#pragma version(1)
#pragma rs java_package_name(de.markusfisch.android.binaryeye.renderscript)
#pragma rs java_package_name(de.markusfisch.android.binaryeye.rs)
#pragma rs_fp_relaxed
rs_allocation inImage;
int inWidth;
int inHeight;
uchar RS_KERNEL rotate90(uchar in, uint32_t x, uint32_t y) {
uchar RS_KERNEL rotate90(const uchar in, uint32_t x, uint32_t y) {
const uchar *out = rsGetElementAt(inImage, y, inHeight - 1 - x);
return *out;
}
uchar RS_KERNEL rotate180(uchar in, uint32_t x, uint32_t y) {
uchar RS_KERNEL rotate180(const uchar in, uint32_t x, uint32_t y) {
const uchar *out = rsGetElementAt(
inImage,
inWidth - 1 - x,
@@ -19,7 +19,7 @@ uchar RS_KERNEL rotate180(uchar in, uint32_t x, uint32_t y) {
return *out;
}
uchar RS_KERNEL rotate270(uchar in, uint32_t x, uint32_t y) {
uchar RS_KERNEL rotate270(const uchar in, uint32_t x, uint32_t y) {
const uchar *out = rsGetElementAt(inImage, inWidth - 1 - y, x);
return *out;
}
@@ -0,0 +1,4 @@
* Export SQLite database
* Improve order of preferences
* Update Italian translation
@@ -0,0 +1,3 @@
* Update Russian translation
* Force compat mode if native RenderScript crashed
@@ -0,0 +1,6 @@
* Share history as CSV and JSON too
* Export history in JSON format
* Update Indonesian translation
* Fix initializing of RenderScript automatically
* Fix highlighting after landscape to landscape rotations
@@ -0,0 +1,2 @@
* Add a setting to enable reading of vertical 1D barcodes
@@ -0,0 +1,8 @@
* Add a handle to define a region of interest
* Add a setting to show/hide cropping limiter
* Add support for VCALENDAR types
* Add spanish translation
* Add copy to clipboard button to context menu in history listing
* Make history actions work on current listing only
* Keep camera selection over orientation changes
@@ -0,0 +1,4 @@
* Improve usability of cropping limiter
* Draw round corners around region of interest
* Update Indonesian translation
@@ -0,0 +1,4 @@
* Stop detection while region of interest is modified
* Fix resetting region of interest
* Add Polish translation
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24H0V0h24v24z" fill="none"/>
<path d="M 4.413 15.067 L 3.533 15.067 L 3.533 16.997 L 2.163 16.997 L 2.163 11.307 L 4.433 11.307 C 4.866 11.307 5.25 11.387 5.583 11.547 C 5.923 11.707 6.183 11.937 6.363 12.237 C 6.55 12.53 6.643 12.864 6.643 13.237 C 6.643 13.79 6.443 14.234 6.043 14.567 C 5.643 14.9 5.1 15.067 4.413 15.067 Z M 3.533 12.367 L 3.533 14.007 L 4.433 14.007 C 4.7 14.007 4.903 13.94 5.043 13.807 C 5.183 13.674 5.253 13.487 5.253 13.247 C 5.253 12.98 5.18 12.767 5.033 12.607 C 4.893 12.447 4.696 12.367 4.443 12.367 L 3.533 12.367 ZM 12.12 11.307 L 12.12 16.997 L 10.76 16.997 L 8.74 13.457 L 8.74 16.997 L 7.37 16.997 L 7.37 11.307 L 8.74 11.307 L 10.75 14.847 L 10.75 11.307 L 12.12 11.307 ZM 17.647 13.957 L 17.647 16.297 C 17.434 16.53 17.124 16.717 16.717 16.857 C 16.31 17.004 15.864 17.077 15.377 17.077 C 14.637 17.077 14.044 16.847 13.597 16.387 C 13.15 15.934 12.91 15.3 12.877 14.487 L 12.877 13.997 C 12.877 13.437 12.977 12.947 13.177 12.527 C 13.37 12.114 13.654 11.794 14.027 11.567 C 14.394 11.34 14.82 11.227 15.307 11.227 C 16.014 11.227 16.564 11.39 16.957 11.717 C 17.344 12.037 17.57 12.52 17.637 13.167 L 16.317 13.167 C 16.27 12.847 16.17 12.62 16.017 12.487 C 15.857 12.354 15.634 12.287 15.347 12.287 C 15.007 12.287 14.744 12.43 14.557 12.717 C 14.364 13.01 14.267 13.427 14.267 13.967 L 14.267 14.317 C 14.267 14.884 14.364 15.31 14.557 15.597 C 14.75 15.877 15.057 16.017 15.477 16.017 C 15.83 16.017 16.094 15.94 16.267 15.787 L 16.267 14.897 L 15.317 14.897 L 15.317 13.957 L 17.647 13.957 Z" transform="matrix(1, 0, 0, 1, 0, 0)" style="white-space: pre;"/>
<path d="M 20.373 6.135 L 17.614 8.888 L 19.684 8.888 L 19.684 11.809 L 21.063 11.809 L 21.063 8.888 L 23.133 8.888 L 20.373 6.135 Z" style="" transform="matrix(0.707107, 0.707107, -0.707107, 0.707107, 12.311422, -11.778402)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24H0V0h24v24z" fill="none"/>
<path d="M 5.45 15.487 C 5.45 15.287 5.38 15.13 5.24 15.017 C 5.1 14.904 4.85 14.787 4.49 14.667 C 4.137 14.547 3.847 14.43 3.62 14.317 C 2.88 13.957 2.51 13.46 2.51 12.827 C 2.51 12.514 2.603 12.237 2.79 11.997 C 2.97 11.757 3.23 11.57 3.57 11.437 C 3.903 11.297 4.28 11.227 4.7 11.227 C 5.107 11.227 5.473 11.3 5.8 11.447 C 6.127 11.594 6.377 11.804 6.55 12.077 C 6.73 12.344 6.82 12.65 6.82 12.997 L 5.46 12.997 C 5.46 12.764 5.387 12.584 5.24 12.457 C 5.1 12.33 4.91 12.267 4.67 12.267 C 4.423 12.267 4.23 12.32 4.09 12.427 C 3.95 12.534 3.88 12.67 3.88 12.837 C 3.88 12.984 3.957 13.117 4.11 13.237 C 4.263 13.357 4.537 13.48 4.93 13.607 C 5.323 13.734 5.647 13.87 5.9 14.017 C 6.513 14.37 6.82 14.857 6.82 15.477 C 6.82 15.977 6.633 16.367 6.26 16.647 C 5.887 16.934 5.373 17.077 4.72 17.077 C 4.26 17.077 3.843 16.994 3.47 16.827 C 3.097 16.66 2.813 16.434 2.62 16.147 C 2.433 15.86 2.34 15.53 2.34 15.157 L 3.72 15.157 C 3.72 15.457 3.797 15.68 3.95 15.827 C 4.11 15.974 4.367 16.047 4.72 16.047 C 4.947 16.047 5.127 15.997 5.26 15.897 C 5.387 15.797 5.45 15.66 5.45 15.487 ZM 8.608 11.307 L 9.738 15.447 L 10.868 11.307 L 12.398 11.307 L 10.488 16.997 L 8.988 16.997 L 7.088 11.307 L 8.608 11.307 ZM 17.422 13.957 L 17.422 16.297 C 17.209 16.53 16.899 16.717 16.492 16.857 C 16.085 17.004 15.639 17.077 15.152 17.077 C 14.412 17.077 13.819 16.847 13.372 16.387 C 12.925 15.934 12.685 15.3 12.652 14.487 L 12.652 13.997 C 12.652 13.437 12.752 12.947 12.952 12.527 C 13.145 12.114 13.429 11.794 13.802 11.567 C 14.169 11.34 14.595 11.227 15.082 11.227 C 15.789 11.227 16.339 11.39 16.732 11.717 C 17.119 12.037 17.345 12.52 17.412 13.167 L 16.092 13.167 C 16.045 12.847 15.945 12.62 15.792 12.487 C 15.632 12.354 15.409 12.287 15.122 12.287 C 14.782 12.287 14.519 12.43 14.332 12.717 C 14.139 13.01 14.042 13.427 14.042 13.967 L 14.042 14.317 C 14.042 14.884 14.139 15.31 14.332 15.597 C 14.525 15.877 14.832 16.017 15.252 16.017 C 15.605 16.017 15.869 15.94 16.042 15.787 L 16.042 14.897 L 15.092 14.897 L 15.092 13.957 L 17.422 13.957 Z" transform="matrix(1, 0, 0, 1, 0, 0)" style="white-space: pre;"/>
<path d="M 19.58 6.075 L 16.821 8.828 L 18.891 8.828 L 18.891 11.749 L 20.27 11.749 L 20.27 8.828 L 22.34 8.828 L 19.58 6.075 Z" style="" transform="matrix(0.707107, 0.707107, -0.707107, 0.707107, 12.036731, -11.23524)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB