Compare commits

...
56 Commits
Author SHA1 Message Date
Markus Fisch c225e7d9eb Advance version number to 1.33.0 2020-07-26 19:25:53 +02:00
Markus Fisch 6a0ce45a65 Optimize copy password button with avocado 2020-07-26 19:15:18 +02:00
Markus Fisch 11c97cc4ef Send scanned code to custom URL in background
As requested in #95.
2020-07-26 19:12:26 +02:00
Markus Fisch e3f6732bb8 Move onDestroyView() below onCreateView()
Better keep these next to each other.
2020-07-23 11:06:15 +02:00
Markus Fisch 53ae706b9c Add a button to copy WiFi password into clipboard
That can't happen automatically anymore since there's now a setting
to automatically put the barcode contents into the clipboard. If that
is enabled, it'd interfere with this.

Also, it's probably better to make it explict anyway.
Not everybody wants to have passwords in the clipboard.
2020-07-22 14:49:15 +02:00
Markus Fisch 26a7ad2e6e Add a setting for putting contents into clipboard
Automatically, always, in the background.

This setting should come with a warning that, if enabled, all your
scans may leak into other apps through the clipboard.

You know, there are apps that continously probe the cliboard in the
background to send any data they find to their server. Nasty stuff.
2020-07-22 14:29:48 +02:00
Markus Fisch 9c3984ce7a Continue with layout even if it didn't change
Sometimes, on some devices, the soft keyboard takes a bit too long
to vanish and `onLayout()` seems to run while the keyboard's height
is still taken into account. Then, the crop handle may sometime end
up being in the wrong position.

I suppose it's either because `onLayout()` is somehow not invoked
again after the soft keyboard is gone, or because the `changed`
parameter is `false` (despite `bottom` has changed).

This may be because the view itself isn't resized by the soft
keyboard (because of View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) but only
gets bottom padding from `WindowInsets` when the soft keyboard is
visible.

So, because this is quite hard to provoke, I will simply remove
doing nothing when `changed` is `true` and simply (re-)calculate
the position of the crop handle for every `onLayout()`.
2020-07-22 14:00:45 +02:00
zmniandGitHub f7d07e0b8a Update Indonesian translation 2020-07-11 15:24:21 +02:00
Markus Fisch 3fe0f50289 Advance version number to 1.32.1 2020-07-04 15:28:44 +02:00
Markus Fisch e86cf9d2cc Remove ROI when handle is dragged to screen corner
As an alternative way to get rid of the ROI.
2020-07-04 15:15:32 +02:00
Markus Fisch 4f1482d2c9 Restrict ROI calculation to screen space
CameraView.previewRect may be larger than the screen (and thus as
the DetectorView which is at most as big as the screen).

If previewRect is larger, its left and/or top coordinate will be
negative which must then be clamped to the screen/DetectorView.
2020-07-04 14:26:21 +02:00
Markus Fisch c42ad7dbea Set ANDROID_NDK_HOME to nothing in gradlew
This is quite a hack to ensure this app builds *without* the NDK
because using the NDK will produce broken builds for Android 6.

For details see:
https://github.com/markusfisch/BinaryEye/issues/111

Unfortunately, it's (currently) not possible to configure Gradle
to simply ignore the NDK. `nkd.dir` cannot be set to nothing or
an invalid value.
2020-07-04 14:09:57 +02:00
Markus Fisch fa3cc8c408 Remove extra action item to create bar code
Since the FAB's now always above the keyboard, there's no
need for an extra action item.
2020-06-29 19:47:27 +02:00
Markus Fisch e4863bad4c Keep soft keyboard from repositioning the layout
Only important for the EncodingFragment.
2020-06-29 19:46:52 +02:00
Markus Fisch 484bbbc997 Add changelog for latest version for f-droid 2020-06-23 21:38:07 +02:00
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
Markus Fisch 62d3fd7e94 Advance version number to 1.30.1 2020-06-02 21:48:56 +02:00
Markus Fisch acdac4ca30 Improve german translation for ROI handle setting 2020-06-02 17:36:19 +02:00
Markus Fisch 221cf11576 Convert ROI handle positions to integer points
Because they're always discrete values and can never be inbetween.

Also, calling `drawBitmap()` with float values can lead to slightly
distorted renderings because the image is interpolated between
pixels then.

So let's just avoid this by using integers.
2020-06-02 16:04:39 +02:00
Markus Fisch d34fceb48f Move ROI button back in place if ROI is too small
It's probably better to restore the original situation than
having the button stay where it is.

Also clean up DetectorView a bit and refactor some variables.
2020-06-02 09:59:18 +02:00
Markus Fisch 4cc4cef4ca Show plus in FAB if flash is not available
So it doesn't look awkward when a device doesn't have a flash.
2020-06-01 22:49:25 +02:00
Markus Fisch 969ffd3d0a Move ROI button above FAB
Looks tidier.
2020-06-01 22:48:42 +02:00
Markus Fisch 81a505c752 Refactor Candidates to Dots
The dots aren't just candidates but may already be result points
so this name doesn't really fit.
2020-06-01 20:04:06 +02:00
Markus Fisch eafcf0dba6 Rename ic_crop_handle to button_crop
It's not an icon but a button.
2020-06-01 19:58:29 +02:00
zmniandGitHub 0539468d3d Update Indonesian translation 2020-06-01 19:48:09 +02:00
Markus Fisch 5e9361ac77 Keep ROI over orientation changes 2020-06-01 10:44:48 +02:00
Markus Fisch 54fb31d6a5 Show a default ROI when button is just tapped
Nobody knows the button is a handle that can be used.

So the natural thing to do is to tap on it which should do the
next best thing and that is showing a default region of interest.

Maybe the button should be positioned above the fab and have a
different color when inactive.
2020-05-31 22:11:43 +02:00
Markus Fisch 0f7da91e77 Draw round corners for ROI box
Looks much better.
2020-05-31 19:29:00 +02:00
Markus Fisch 13bbb3e435 Update build tools and gradle wrapper 2020-05-31 13:15:46 +02:00
87 changed files with 1100 additions and 1324 deletions
+29
View File
@@ -1,5 +1,34 @@
# Change Log
## 1.33.0
* Add a setting to sending scanned codes to a custom URL in background
* Add a setting to automatically put contents into clipboard
* Add an explicit button to copy WiFi password into clipboard
* Update Indonesian translation
## 1.32.1
* Alternatively remove region of interest by dragging handle to a screen corner
* Restrict region of interest to screen space
* Remove extra action item to create barcode
## 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
* Update Indonesian translation
## 1.30.0
* Add a handle to define a region of interest
* Add a setting to show/hide cropping limiter
+2 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 66
versionName '1.30.0'
versionCode 71
versionName '1.33.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
+2
View File
@@ -7,6 +7,7 @@
android:xlargeScreens="true"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
@@ -16,6 +17,7 @@
<application
tools:ignore="UnusedAttribute"
android:name=".app.BinaryEyeApp"
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules"
android:supportsRtl="true"
@@ -10,6 +10,7 @@ import android.hardware.Camera
import android.net.Uri
import android.os.Bundle
import android.os.Vibrator
import android.support.design.widget.FloatingActionButton
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.support.v8.renderscript.RSRuntimeException
@@ -23,17 +24,17 @@ 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
import de.markusfisch.android.binaryeye.net.send
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
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
@@ -53,7 +54,7 @@ class CameraActivity : AppCompatActivity() {
private lateinit var cameraView: CameraView
private lateinit var detectorView: DetectorView
private lateinit var zoomBar: SeekBar
private lateinit var flashFab: View
private lateinit var flashFab: FloatingActionButton
private var preprocessor: Preprocessor? = null
private var nativeMapping: Mapping? = null
@@ -62,8 +63,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(
@@ -72,7 +76,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)
@@ -110,13 +114,11 @@ class CameraActivity : AppCompatActivity() {
cameraView = findViewById(R.id.camera_view) as CameraView
detectorView = findViewById(R.id.detector_view) as DetectorView
zoomBar = findViewById(R.id.zoom) as SeekBar
flashFab = findViewById(R.id.flash)
flashFab.setOnClickListener { toggleTorchMode() }
flashFab = findViewById(R.id.flash) as FloatingActionButton
initCameraView()
initZoomBar()
restoreZoom()
detectorView.updateRoi = { recreatePreprocessor = true }
initDetectorView()
if (intent?.action == Intent.ACTION_SEND &&
intent.type == "text/plain"
@@ -130,6 +132,7 @@ class CameraActivity : AppCompatActivity() {
resetPreProcessor()
fallbackBuffer = null
saveZoom()
saveCropHandlePos()
}
override fun onResume() {
@@ -137,7 +140,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()
}
}
@@ -176,24 +179,36 @@ 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
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.create -> {
startActivity(MainActivity.getEncodeIntent(this))
createBarcode()
true
}
R.id.history -> {
@@ -216,6 +231,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
@@ -228,6 +248,10 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun createBarcode() {
startActivity(MainActivity.getEncodeIntent(this))
}
private fun switchCamera() {
closeCamera()
frontFacing = frontFacing xor true
@@ -341,7 +365,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(
@@ -350,8 +375,10 @@ class CameraActivity : AppCompatActivity() {
frameHeight,
frameOrientation
)?.let { result ->
postResult(result)
decoding = false
if (result.text != ignoreNext) {
postResult(result)
decoding = false
}
}
}
}
@@ -381,6 +408,7 @@ class CameraActivity : AppCompatActivity() {
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
restoreZoom()
}
@Suppress("DEPRECATION")
@@ -412,11 +440,40 @@ class CameraActivity : AppCompatActivity() {
)
}
private fun updateFlashFab(available: Boolean) {
flashFab.visibility = if (available) {
View.GONE
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)
flashFab.setOnClickListener { createBarcode() }
} else {
View.VISIBLE
flashFab.setImageResource(R.drawable.ic_action_flash)
flashFab.setOnClickListener { toggleTorchMode() }
}
}
@@ -534,14 +591,19 @@ class CameraActivity : AppCompatActivity() {
) {
null
} else {
// map ROI in detectorView to cameraView because cameraView may
// be larger than the detectorView
// map ROI in detectorView to cameraView
val previewRect = cameraView.previewRect
// previewRect may be larger than the screen (and thus as the
// detectorView) in which case its left and/or top coordinate
// will be negative which must then be clamped to the
// screen/DetectorView
val previewLeft = max(0, previewRect.left)
val previewTop = max(0, previewRect.top)
val previewRoi = Rect(
previewRect.left + viewRoi.left,
previewRect.top + viewRoi.top,
previewRect.left + viewRoi.right,
previewRect.top + viewRoi.bottom
previewLeft + viewRoi.left,
previewTop + viewRoi.top,
previewLeft + viewRoi.right,
previewTop + viewRoi.bottom
)
val previewRectWidth = previewRect.width().toFloat()
val previewRectHeight = previewRect.height().toFloat()
@@ -583,37 +645,52 @@ 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
}
if (prefs.copyImmediately) {
activity.copyToClipboard(result.text)
}
val scan = Scan(result)
prefs.sendScanUrl?.let {
scan.send(it)
}
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
}
@@ -7,6 +7,7 @@ import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.view.WindowManager
import android.widget.AbsListView
import de.markusfisch.android.binaryeye.R
@@ -50,7 +51,12 @@ private fun lastChildOutOfView(listView: AbsListView): Boolean {
fun initSystemBars(activity: AppCompatActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.decorView.systemUiVisibility =
// keeps the soft keyboard from repositioning the layout
val window = activity.window
window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -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(
@@ -113,15 +115,6 @@ class DecodeFragment : Fragment() {
hexView = view.findViewById(R.id.hex)
updateViewsAndAction(raw)
if (action is WifiAction) {
val password = (action as WifiAction).password
if (password != null) {
activity?.apply {
copyToClipboard(password)
toast(R.string.copied_password_to_clipboard)
}
}
}
if (prefs.showMetaData) {
fillMetaView(metaView, scan)
@@ -133,6 +126,11 @@ class DecodeFragment : Fragment() {
return view
}
override fun onDestroyView() {
parentJob.cancel()
super.onDestroyView()
}
private fun updateViewsAndAction(bytes: ByteArray) {
val prevAction = action
if (!prevAction.canExecuteOn(bytes)) {
@@ -160,7 +158,7 @@ class DecodeFragment : Fragment() {
private fun fillMetaView(tableLayout: TableLayout, scan: Scan) {
val ctx = tableLayout.context
val spaceBetween = (ctx.resources.displayMetrics.density * 16f).toInt()
val spaceBetween = (16f * ctx.resources.displayMetrics.density).toInt()
var hasMeta = false
sortedMapOf(
R.string.error_correction_level to scan.errorCorrectionLevel,
@@ -196,10 +194,20 @@ 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
}
if (action is WifiAction) {
menu.findItem(R.id.copy_password).isVisible = true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.copy_password -> {
copyPasswordToClipboard()
true
}
R.id.copy_to_clipboard -> {
copyToClipboard(content)
true
@@ -215,10 +223,27 @@ class DecodeFragment : Fragment() {
)
true
}
R.id.remove -> {
db.removeScan(id)
backOrFinish()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun copyPasswordToClipboard() {
val ac = action
if (ac is WifiAction) {
ac.password?.let { password ->
activity?.apply {
copyToClipboard(password)
toast(R.string.copied_password_to_clipboard)
}
}
}
}
private fun copyToClipboard(text: String) {
activity?.apply {
copyToClipboard(text)
@@ -226,6 +251,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()) {
@@ -259,11 +293,6 @@ class DecodeFragment : Fragment() {
}
}
override fun onDestroyView() {
parentJob.cancel()
super.onDestroyView()
}
companion object {
private const val SCAN = "scan"
@@ -31,11 +31,6 @@ class EncodeFragment : Fragment() {
BarcodeFormat.UPC_A
)
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -94,20 +89,6 @@ class EncodeFragment : Fragment() {
prefs.indexOfLastSelectedFormat = formatView.selectedItemPosition
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_encode, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.next -> {
encode()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun encode() {
val format = writers[formatView.selectedItemPosition]
val size = getSize(sizeBarView.progress)
@@ -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
@@ -20,9 +20,11 @@ class PreferencesFragment : Fragment() {
private lateinit var useHistorySwitch: SwitchCompat
private lateinit var ignoreConsecutiveDuplicatesSwitch: SwitchCompat
private lateinit var openImmediatelySwitch: SwitchCompat
private lateinit var copyImmediatelySwitch: SwitchCompat
private lateinit var showMetaDataSwitch: SwitchCompat
private lateinit var showHexDumpSwitch: SwitchCompat
private lateinit var openWithUrlInput: EditText
private lateinit var sendScanUrlInput: EditText
override fun onCreateView(
inflater: LayoutInflater,
@@ -79,6 +81,11 @@ class PreferencesFragment : Fragment() {
openImmediatelySwitch.toggle()
}
copyImmediatelySwitch = view.findViewById(R.id.copy_immediately)
if (prefs.copyImmediately) {
copyImmediatelySwitch.toggle()
}
showMetaDataSwitch = view.findViewById(R.id.show_meta_data)
if (prefs.showMetaData) {
showMetaDataSwitch.toggle()
@@ -92,6 +99,9 @@ class PreferencesFragment : Fragment() {
openWithUrlInput = view.findViewById(R.id.open_with_url)
openWithUrlInput.setText(prefs.openWithUrl)
sendScanUrlInput = view.findViewById(R.id.send_scan_url)
sendScanUrlInput.setText(prefs.sendScanUrl)
(view.findViewById(R.id.scroll_view) as View).setPaddingFromWindowInsets()
return view
@@ -107,8 +117,10 @@ class PreferencesFragment : Fragment() {
prefs.useHistory = useHistorySwitch.isChecked
prefs.ignoreConsecutiveDuplicates = ignoreConsecutiveDuplicatesSwitch.isChecked
prefs.openImmediately = openImmediatelySwitch.isChecked
prefs.copyImmediately = copyImmediatelySwitch.isChecked
prefs.showMetaData = showMetaDataSwitch.isChecked
prefs.showHexDump = showHexDumpSwitch.isChecked
prefs.openWithUrl = openWithUrlInput.text.toString()
prefs.sendScanUrl = sendScanUrlInput.text.toString()
}
}
@@ -0,0 +1,15 @@
package de.markusfisch.android.binaryeye.graphics
import android.content.Context
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.support.v4.content.ContextCompat
import de.markusfisch.android.binaryeye.R
fun Context.getDashedBorderPaint() = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val dp = resources.displayMetrics.density
color = ContextCompat.getColor(applicationContext, R.color.crop_bound)
style = Paint.Style.STROKE
strokeWidth = dp * 2f
pathEffect = DashPathEffect(floatArrayOf(10f * dp, 10f * dp), 0f)
}
@@ -7,15 +7,12 @@ import android.graphics.Point
import android.support.v4.content.ContextCompat
import de.markusfisch.android.binaryeye.R
class Candidates(context: Context) {
class Dots(context: Context) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val radius = 8f * context.resources.displayMetrics.density
init {
paint.color = ContextCompat.getColor(
context,
R.color.candidate
)
paint.color = ContextCompat.getColor(context, R.color.dot)
paint.style = Paint.Style.FILL
}
@@ -0,0 +1,24 @@
package de.markusfisch.android.binaryeye.net
import de.markusfisch.android.binaryeye.database.Scan
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import kotlin.concurrent.thread
fun Scan.send(url: String) {
thread {
var con: HttpURLConnection? = null
try {
con = URL(
url + URLEncoder.encode(content, "utf-8")
).openConnection() as HttpURLConnection
con.responseCode
} catch (e: IOException) {
// can't do anything about it
} finally {
con?.disconnect()
}
}
}
@@ -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)
@@ -46,6 +61,11 @@ class Preferences {
apply(IGNORE_CONSECUTIVE_DUPLICATES, value)
field = value
}
var copyImmediately = false
set(value) {
apply(COPY_IMMEDIATELY, value)
field = value
}
var openImmediately = false
set(value) {
apply(OPEN_IMMEDIATELY, value)
@@ -66,6 +86,11 @@ class Preferences {
apply(OPEN_WITH_URL, value)
field = value
}
var sendScanUrl: String = ""
set(value) {
apply(POST_SCAN_URL, value)
field = value
}
var indexOfLastSelectedFormat: Int = 0
set(value) {
apply(INDEX_OF_LAST_SELECTED_FORMAT, value)
@@ -85,6 +110,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)
@@ -95,6 +126,10 @@ class Preferences {
IGNORE_CONSECUTIVE_DUPLICATES,
ignoreConsecutiveDuplicates
)
copyImmediately = preferences.getBoolean(
COPY_IMMEDIATELY,
copyImmediately
)
openImmediately = preferences.getBoolean(
OPEN_IMMEDIATELY,
openImmediately
@@ -104,6 +139,9 @@ class Preferences {
preferences.getString(OPEN_WITH_URL, openWithUrl)?.also {
openWithUrl = it
}
preferences.getString(POST_SCAN_URL, sendScanUrl)?.also {
sendScanUrl = it
}
indexOfLastSelectedFormat = preferences.getInt(
INDEX_OF_LAST_SELECTED_FORMAT,
indexOfLastSelectedFormat
@@ -131,6 +169,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"
@@ -139,9 +180,11 @@ class Preferences {
const val USE_HISTORY = "use_history"
const val IGNORE_CONSECUTIVE_DUPLICATES = "ignore_consecutive_duplicates"
const val OPEN_IMMEDIATELY = "open_immediately"
const val COPY_IMMEDIATELY = "copy_immediately"
const val SHOW_HEX_DUMP = "show_hex_dump"
const val SHOW_META_DATA = "show_meta_data"
const val OPEN_WITH_URL = "open_with_url"
const val POST_SCAN_URL = "post_scan_url"
const val INDEX_OF_LAST_SELECTED_FORMAT = "index_of_last_selected_format"
const val FORCE_COMPAT = "force_compat"
}
@@ -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
)
@@ -143,7 +147,7 @@ class Preprocessor(
0, 0,
roi.width(), roi.height(),
yuvAlloc,
roi.left, roi.top
max(0, roi.left), max(0, roi.top)
)
roiAlloc
} else {
@@ -1,41 +1,38 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.graphics.*
import android.support.v4.content.ContextCompat
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.graphics.Candidates
import de.markusfisch.android.binaryeye.graphics.Dots
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import kotlin.math.roundToInt
class CropImageView(context: Context, attr: AttributeSet) :
ConfinedScalingImageView(context, attr) {
val windowInsets = Rect()
var onScan: (() -> List<Point>?)? = null
var onScan: (() -> Unit)? = null
private val candidates = Candidates(context)
private val boundsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val dots = Dots(context)
private val boundsPaint = context.getDashedBorderPaint()
private val lastMappedRect = RectF()
private val dp = context.resources.displayMetrics.density
private val padding: Int = (dp * 24f).roundToInt()
private val onScanRunnable = Runnable {
onScan?.invoke()?.let {
candidatePoints = it
invalidate()
}
}
private val padding: Int = (24f * context.resources.displayMetrics.density).roundToInt()
private val onScanRunnable = Runnable { onScan?.invoke() }
private var candidatePoints: List<Point>? = null
private var resultPoints: List<Point>? = null
init {
boundsPaint.color = ContextCompat.getColor(context, R.color.crop_bound)
boundsPaint.style = Paint.Style.STROKE
boundsPaint.strokeWidth = dp * 2f
boundsPaint.pathEffect = DashPathEffect(floatArrayOf(dp * 10f, dp * 10f), 0f)
scaleType = ScaleType.CENTER_CROP
}
fun updateResultPoints(points: List<Point>) {
resultPoints = points
invalidate()
}
override fun onLayout(
changed: Boolean,
left: Int,
@@ -85,10 +82,10 @@ class CropImageView(context: Context, attr: AttributeSet) :
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(bounds, boundsPaint)
candidatePoints?.let {
candidates.draw(canvas, it)
resultPoints?.let {
dots.draw(canvas, it)
}
candidatePoints = null
resultPoints = null
val mr = mappedRect ?: return
if (mr != lastMappedRect) {
removeCallbacks(onScanRunnable)
@@ -4,51 +4,67 @@ import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.graphics.Candidates
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.round
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 candidates = Candidates(context)
private val orientation = resources.configuration.orientation
private val dots = Dots(context)
private val invalidateRunnable: Runnable = Runnable {
marks = null
invalidate()
}
private val roiPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val roiPaint = context.getDashedBorderPaint()
private val handleBitmap = resources.getBitmapFromDrawable(
R.drawable.ic_crop_handle
R.drawable.button_crop
)
private val handleXRadius = handleBitmap.width / 2
private val handleYRadius = handleBitmap.height / 2
private val distToFull: Float
private val handlePos = Point(-1, -1)
private val handleHome = Point()
private val center = Point()
private val touchDown = Point()
private val distToFull: Int
private val minMoveThresholdSq: Int
private val cornerRadius: Int
private val fabHeight: Int
private val padding: Int
private var marks: List<Point>? = null
private var center = PointF()
private var handlePos = PointF()
private var handleGrabbed = false
private var handleActive = false
private var minY = 0
private var maxY = 0
private var minDist = 0
private var shadeColor = 0
init {
val dp = context.resources.displayMetrics.density
distToFull = 24f * dp
roiPaint.apply {
style = Paint.Style.STROKE
color = 0xffffffff.toInt()
strokeWidth = 2f * dp
pathEffect = DashPathEffect(floatArrayOf(10f, 20f), 0f)
}
distToFull = (24f * dp).roundToInt()
val minMoveThreshold = (8f * dp).roundToInt()
minMoveThresholdSq = minMoveThreshold * minMoveThreshold
cornerRadius = (8f * dp).roundToInt()
fabHeight = (92f * dp).roundToInt()
padding = (20f * dp).roundToInt()
isSaveEnabled = true
}
constructor(context: Context, attrs: AttributeSet) :
@@ -57,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()
@@ -64,14 +97,45 @@ class DetectorView : View {
postDelayed(invalidateRunnable, 500)
}
override fun onSaveInstanceState(): Parcelable? {
if (!handleActive) {
return super.onSaveInstanceState()
}
return SavedState(super.onSaveInstanceState()).apply {
savedHandlePos.set(getCropHandlePos())
savedOrientation = currentOrientation
}
}
override fun onRestoreInstanceState(state: Parcelable) {
super.onRestoreInstanceState(
if (state is SavedState) {
setCropHandlePos(
state.savedHandlePos.x,
state.savedHandlePos.y,
state.savedOrientation
)
state.superState
} else {
state
}
)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
event ?: return super.onTouchEvent(event)
val x = event.x.roundToInt()
val y = event.y.roundToInt()
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (prefs.showCropHandle) {
handleGrabbed = abs(event.x - handlePos.x) < handleXRadius &&
abs(event.y - handlePos.y) < handleYRadius
touchDown.set(x, y)
handleGrabbed = abs(x - handlePos.x) < handleXRadius &&
abs(y - handlePos.y) < handleYRadius
if (handleGrabbed) {
onRoiChange?.invoke()
}
handleGrabbed
} else {
false
@@ -79,7 +143,11 @@ class DetectorView : View {
}
MotionEvent.ACTION_MOVE -> {
if (handleGrabbed) {
handlePos.set(event.x, event.y)
handlePos.set(x, y)
if (distSq(handlePos, touchDown) > minMoveThresholdSq) {
handleActive = true
}
updateClipRect()
invalidate()
true
} else {
@@ -88,15 +156,26 @@ class DetectorView : View {
}
MotionEvent.ACTION_CANCEL -> {
if (handleGrabbed) {
snap(event.x, event.y)
snap(x, y)
handleGrabbed = false
}
false
}
MotionEvent.ACTION_UP -> {
if (handleGrabbed) {
snap(event.x, event.y)
updateRoi?.invoke()
if (!handleActive) {
val mn = min(center.x, center.y) * .8f
handlePos.set(
(center.x + mn).roundToInt(),
(center.y + mn).roundToInt()
)
handleActive = true
invalidate()
} else {
snap(x, y)
}
updateClipRect()
onRoiChanged?.invoke()
handleGrabbed = false
}
false
@@ -105,78 +184,195 @@ class DetectorView : View {
}
}
private fun snap(x: Float, y: Float) {
if (abs(x - center.x) < distToFull) {
handlePos.x = center.x
invalidate()
}
if (abs(y - center.y) < distToFull) {
handlePos.y = center.y
private fun snap(x: Int, y: Int) {
val cx = clampX(x)
val cy = clampY(y)
val dx = abs(cx - center.x)
val dy = abs(cy - center.y)
// check if handle is close to the vertical or horizontal center line
if (dx < distToFull ||
dy < distToFull ||
// check if handle is close to a screen corner
(
(abs(cy - minY) < distToFull || abs(maxY - cy) < distToFull) &&
abs(dx - center.x) < distToFull
)
) {
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)
val width = right - left
val height = bottom - top
center.set(
(left + (width / 2)).toFloat(),
(top + (height / 2)).toFloat()
left + (width / 2),
top + (height / 2)
)
if (width > height) {
handlePos.set(round(right * .75f), center.y)
minY = padding * 2
maxY = height - minY
handleHome.set(
width - handleXRadius - paddingRight - padding,
height - handleYRadius - paddingBottom - fabHeight
)
if (handleActive) {
updateClipRect()
} else {
handlePos.set(center.x, round(bottom * .75f))
reset()
}
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
updateClipRect()
if (roi.height() > 0 && roi.width() > 0) {
// canvas.clipRect() doesn't work reliably below KITKAT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
canvas.save()
canvas.clipOutRectCompat(roi)
canvas.drawColor(shadeColor, PorterDuff.Mode.SRC)
canvas.restore()
} else {
canvas.drawRect(roi, roiPaint)
}
if (handleActive) {
drawClip(canvas)
}
marks?.let {
candidates.draw(canvas, it)
dots.draw(canvas, it)
}
if (prefs.showCropHandle) {
canvas.drawBitmap(
handleBitmap,
handlePos.x - handleXRadius,
handlePos.y - handleYRadius,
(handlePos.x - handleXRadius).toFloat(),
(handlePos.y - handleYRadius).toFloat(),
null
)
}
}
private fun drawClip(canvas: Canvas) {
if (minDist < 1) {
return
}
// canvas.clipRect() doesn't work reliably below KITKAT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val radius = min(minDist / 2, cornerRadius).toFloat()
canvas.save()
canvas.clipOutPathCompat(
calculateRoundedRectPath(
roi.left.toFloat(),
roi.top.toFloat(),
roi.right.toFloat(),
roi.bottom.toFloat(),
radius,
radius
)
)
canvas.drawColor(shadeColor, PorterDuff.Mode.SRC)
canvas.restore()
} else {
canvas.drawRect(roi, roiPaint)
}
}
private fun updateClipRect() {
clampHandlePos()
val dx = abs(handlePos.x - center.x)
val dy = abs(handlePos.y - center.y)
val d = min(dx, dy)
shadeColor = (min(1f, d / distToFull) * 128f).toInt() shl 24
minDist = min(dx, dy)
shadeColor = (min(
1f,
minDist.toFloat() / distToFull.toFloat()
) * 128f).toInt() shl 24
roi.set(
(center.x - dx).roundToInt(),
(center.y - dy).roundToInt(),
(center.x + dx).roundToInt(),
(center.y + dy).roundToInt()
center.x - dx,
center.y - dy,
center.x + dx,
center.y + dy
)
}
private fun clampHandlePos() {
handlePos.x = clampX(handlePos.x)
handlePos.y = clampY(handlePos.y)
}
private fun clampX(x: Int) = min(center.x * 2, max(0, x))
private fun clampY(y: Int) = min(maxY, max(minY, y))
internal class SavedState : BaseSavedState {
val savedHandlePos = Point()
var savedOrientation = 0
constructor(superState: Parcelable?) : super(superState)
private constructor(parcel: Parcel) : super(parcel) {
savedHandlePos.set(
parcel.readInt(),
parcel.readInt()
)
savedOrientation = parcel.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(savedHandlePos.x)
out.writeInt(savedHandlePos.y)
out.writeInt(savedOrientation)
}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel) = SavedState(source)
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
}
}
}
}
private fun Canvas.clipOutRectCompat(rect: Rect) {
private fun distSq(a: Point, b: Point): Int {
val dx = a.x - b.x
val dy = a.y - b.y
return dx * dx + dy * dy
}
private fun Point.set(point: Point) {
x = point.x
y = point.y
}
private fun Canvas.clipOutPathCompat(path: Path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
clipOutRect(rect)
clipOutPath(path)
} else {
@Suppress("DEPRECATION")
clipRect(rect, Region.Op.DIFFERENCE)
clipPath(path, Region.Op.DIFFERENCE)
}
}
private fun calculateRoundedRectPath(
left: Float,
top: Float,
right: Float,
bottom: Float,
rx: Float,
ry: Float
): Path {
val width = right - left
val height = bottom - top
val widthMinusCorners = width - 2 * rx
val heightMinusCorners = height - 2 * ry
return Path().apply {
moveTo(right, top + ry)
rQuadTo(0f, -ry, -rx, -ry)
rLineTo(-widthMinusCorners, 0f)
rQuadTo(-rx, 0f, -rx, ry)
rLineTo(0f, heightMinusCorners)
rQuadTo(0f, ry, rx, ry)
rLineTo(widthMinusCorners, 0f)
rQuadTo(rx, 0f, rx, -ry)
rLineTo(0f, -heightMinusCorners)
close()
}
}
@@ -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()
}
}
}
+12
View File
@@ -0,0 +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="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 android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/>
</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>
@@ -1,26 +0,0 @@
<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>
@@ -58,6 +58,12 @@
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/open_immediately"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/copy_immediately"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/copy_immediately"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/show_meta_data"
android:layout_width="match_parent"
@@ -86,5 +92,21 @@
android:inputType="textUri"
android:hint="@string/open_with_url_hint"
android:importantForAutofill="no"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/send_scan_url"/>
<EditText
tools:targetApi="o"
android:id="@+id/send_scan_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textUri"
android:hint="@string/send_scan_url_hint"
android:importantForAutofill="no"/>
</LinearLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
@@ -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>
+12
View File
@@ -1,6 +1,18 @@
<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="ifRoom"/>
<item
android:id="@+id/copy_password"
android:title="@string/copy_password"
android:icon="@drawable/ic_action_copy_password"
android:visible="false"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/copy_to_clipboard"
android:title="@string/copy_to_clipboard"
@@ -1,9 +0,0 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/next"
android:title="@string/encode"
android:icon="@drawable/ic_action_next"
material:showAsAction="ifRoom"/>
</menu>
+7 -1
View File
@@ -22,6 +22,7 @@
<string name="share">Teilen</string>
<string name="copy_to_clipboard">In die Zwischenablage kopieren</string>
<string name="copied_to_clipboard">Inhalt wurde in die Zwischenablage kopiert</string>
<string name="copy_password">Passwort in die Zwischenablage kopieren</string>
<string name="copied_password_to_clipboard">Passwort wurde in die Zwischenablage kopiert</string>
<string name="open_url">URL öffnen</string>
<string name="cannot_resolve_action">Keine App kann das öffnen</string>
@@ -36,9 +37,10 @@
<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">Ausschnittseinschränker anzeigen</string>
<string name="show_crop_handle">Bereich verkleinern anzeigen</string>
<string name="zoom_by_swiping">Hoch/Runter streichen um zu zoomen</string>
<string name="auto_rotate">Vertikale Erkennung von 1D Barcodes</string>
<string name="try_harder">Suche länger und gründlicher nach Barcodes</string>
@@ -46,9 +48,11 @@
<string name="use_history">Codes speichern</string>
<string name="ignore_consecutive_duplicates">Keine aufeinanderfolgenden Duplikate speichern</string>
<string name="open_immediately">Inspektion überspringen und Inhalte sofort öffnen</string>
<string name="copy_immediately">Inhalte automatisch in Zwischenablage legen</string>
<string name="show_meta_data">Meta Daten anzeigen</string>
<string name="show_hex_dump">Hex Dump anzeigen</string>
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
<string name="send_scan_url">URL die für jeden Scan aufgerufen werden soll</string>
<string name="really_remove_scan">Code wirklich entfernen?</string>
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
<string name="really_remove_selected_scans">Alle ausgewählten Codes entfernen?</string>
@@ -83,6 +87,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>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Compartir</string>
<string name="copy_to_clipboard">Copiar al portapapeles</string>
<string name="copied_to_clipboard">Copiado al portapapeles</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Contraseña copiada al portapapeles</string>
<string name="open_url">Abrir URL</string>
<string name="cannot_resolve_action">Ninguna aplicación puede abrir esto</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Guardar historial de escaneados</string>
<string name="ignore_consecutive_duplicates">No guardar duplicados consecutivos</string>
<string name="open_immediately">Saltar la inspección y abrir el contenido inmediatamente</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Mostrar metadatos</string>
<string name="show_hex_dump">Mostrar volcado hexagonal</string>
<string name="open_with_url">Abrir datos desconocidos con URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">¿Realmente desea borrar el escaneo seleccionado?</string>
<string name="really_remove_all_scans">¿Realmente desea borrar todo lo escaneado?</string>
<string name="really_remove_selected_scans">¿Realmente desea borrar los escaneos seleccionados?</string>
@@ -83,6 +87,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>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Share</string>
<string name="copy_to_clipboard">Copier dans le presse-papier</string>
<string name="copied_to_clipboard">Mis dans le presse-papiers</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">Ouvrir l\'url</string>
<string name="cannot_resolve_action">Aucune application ne peut ouvrir ça</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Sauvegarder l\'histoire des scans</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Supprimer le scan?</string>
<string name="really_remove_all_scans">Supprimer tous les scans?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -83,6 +87,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>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Megosztás</string>
<string name="copy_to_clipboard">Másolás a vágólapra</string>
<string name="copied_to_clipboard">Vágólapra helyezés</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">URL megnyitása</string>
<string name="cannot_resolve_action">Nincs alkalmazás, amely meg tudná nyitni azt</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Beolvasási előzmények mentése</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Vizsgálat kihagyása és a tartalom azonnali megnyitása</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="open_with_url">Ismeretlen adat megnyitása URL-lel</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -83,6 +87,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>
+10 -4
View File
@@ -21,6 +21,7 @@
<string name="share">Bagikan</string>
<string name="copy_to_clipboard">Salin ke papan klip</string>
<string name="copied_to_clipboard">Simpan ke papan klip</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Sandi disalin ke papan klip</string>
<string name="open_url">Buka url</string>
<string name="cannot_resolve_action">Tidak ada aplikasi yang bisa membukanya</string>
@@ -35,24 +36,27 @@
<string name="view_barcode">Lihat barcode</string>
<string name="info">Info</string>
<string name="switch_camera">Ganti kamera</string>
<string name="bulk_mode">Pindai tanpa henti</string>
<string name="history">Riwayat</string>
<string name="preferences">Preferensi</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="show_crop_handle">Tampilkan pembatas pemotongan</string>
<string name="zoom_by_swiping">Perbesar kamera dengan menggeser ke atas/bawah</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="auto_rotate">Kenali barcode 1D secara vertikal</string>
<string name="try_harder">Optimalkan akurasi, bukan kecepatan</string>
<string name="vibrate">Bergetar saat mendeteksi</string>
<string name="use_history">Simpan riwayat pemindaian</string>
<string name="ignore_consecutive_duplicates">Jangan simpan duplikat berturutan</string>
<string name="open_immediately">Lewati inspeksi dan segera buka konten</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Tampilkan metadata</string>
<string name="show_hex_dump">Tampilkan hex dump</string>
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="really_remove_selected_scans">Yakin menghapus semua pemindaian?</string>
<string name="clear_history">Hapus riwayat</string>
<string name="copy_scan">Copy scan</string>
<string name="copy_scan">Salin pemindaian</string>
<string name="edit_scan">Sunting label</string>
<string name="remove_scan">Buang pindaian</string>
<string name="enter_name">Masukkan keterangan pemindaian</string>
@@ -82,6 +86,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>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Condividi</string>
<string name="copy_to_clipboard">Copia negli appunti</string>
<string name="copied_to_clipboard">Metti negli appunti</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copiata negli appunti</string>
<string name="open_url">Apri URL</string>
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Salva cronologia delle scansioni</string>
<string name="ignore_consecutive_duplicates">Non salvare doppi consecutivi</string>
<string name="open_immediately">Salta l\'analisi e apri i contenuti direttamente</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Mostra metadati</string>
<string name="show_hex_dump">Mostra dump esadecimale</string>
<string name="open_with_url">Apri dati sconosciuti con URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Vuoi veramente rimuovere la scansione?</string>
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -83,6 +87,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>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Delen</string>
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
<string name="copied_to_clipboard">Kopieer de inhoud naar het klembord</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">URL openen</string>
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
@@ -36,6 +37,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>
@@ -46,6 +48,7 @@
<string name="use_history">Scan-geschiedenis behouden</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="really_remove_scan">Scan echt verwijderen?</string>
@@ -79,6 +82,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>
@@ -86,6 +91,7 @@
<string name="export_database">Export SQLite database</string>
<string name="error_saving_file">Kon bestand niet opslaan</string>
<string name="open_with_url">Niet herkende data met URL openen</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="pick_list_separator">Hoe de lijstonderdelen te separeren?</string>
<string name="saved_in_downloads">In downloads opgeslagen</string>
<string name="vevent_failed">Kon niet aan kalender toevoegen</string>
+100
View File
@@ -0,0 +1,100 @@
<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="copy_immediately">Automatically copy contents into clipboard</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="send_scan_url">Send every scan to URL</string>
<string name="copy_password">Copy password into clipboard</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>
@@ -22,6 +22,7 @@
<string name="share">Compartilhar</string>
<string name="copy_to_clipboard">Copiar para área de transferência</string>
<string name="copied_to_clipboard">Colocar na área de transferência</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">Abrir url</string>
<string name="cannot_resolve_action">Nenhuma aplicativo pode abrir isso</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Salvar histórico de escaneamento</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="open_with_url">Abrir dados desconhecidos com URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Realmente deseja remover o escaneamento?</string>
<string name="really_remove_all_scans">Realmente deseja remover todos escaneamentos?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -83,6 +87,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>
+6 -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>
@@ -26,6 +24,7 @@
<string name="share">Поделиться</string>
<string name="copy_to_clipboard">Скопировать в буфер обмена</string>
<string name="copied_to_clipboard">Скопирован в буфер обмена</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Пароль скопирован в буфер обмена</string>
<string name="open_url">Открыть URL</string>
<string name="cannot_resolve_action">Не найдено приложение для этого действия</string>
@@ -40,6 +39,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>
@@ -50,9 +50,11 @@
<string name="use_history">Сохранять историю сканирования</string>
<string name="ignore_consecutive_duplicates">Не сохранять последовательные дубликаты</string>
<string name="open_immediately">Пропускать проверку и сразу открыть содержимое</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Показывать метаданные</string>
<string name="show_hex_dump">Показать шестнадцатеричный дамп</string>
<string name="open_with_url">Открыть неизвестные данные с помощью URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Действительно удалить сканирование?</string>
<string name="really_remove_all_scans">Действительно удалить все сканирования?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -87,6 +89,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>
@@ -21,6 +21,7 @@
<string name="share">分享</string>
<string name="copy_to_clipboard">复制到剪切板</string>
<string name="copied_to_clipboard">复制到剪切板</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">打开链接</string>
<string name="cannot_resolve_action">没有相关APP可以打开它</string>
@@ -35,6 +36,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>
@@ -45,9 +47,11 @@
<string name="use_history">保存扫描历史</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">跳过内容展示并立即打开</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="open_with_url">使用URL打开未知数据</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">确定删除记录?</string>
<string name="really_remove_all_scans">确定删除全部记录?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -82,6 +86,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>
+7 -1
View File
@@ -21,6 +21,7 @@
<string name="share">分享</string>
<string name="copy_to_clipboard">複製</string>
<string name="copied_to_clipboard">複製</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">開啓</string>
<string name="cannot_resolve_action">無應用程式可以開啓</string>
@@ -35,6 +36,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>
@@ -44,6 +46,7 @@
<string name="use_history">儲存掃描歷史</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="really_remove_scan">真的要移除掃描記錄?</string>
@@ -77,11 +80,14 @@
<string name="vevent_add">添加至行事曆</string>
<string name="vevent_failed">不能添加至行事曆</string>
<string name="preferences">Preferences</string>
<string name="open_with_url">Preferences</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="otpauth_add">Add 2FA</string>
<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 -1
View File
@@ -9,5 +9,5 @@
<color name="selected_row">#222</color>
<color name="separator">#333</color>
<color name="crop_bound">#fff</color>
<color name="candidate">#80b6d46f</color>
<color name="dot">#80b6d46f</color>
</resources>
@@ -1,5 +1,6 @@
<resources>
<string name="app_name" translatable="false">Binary Eye</string>
<string name="open_with_url_hint" translatable="false">http://example.com?data=</string>
<string name="send_scan_url_hint" translatable="false">http://example.com?data=</string>
<string name="project_url" translatable="false">https://github.com/markusfisch/BinaryEye</string>
</resources>
+6
View File
@@ -22,6 +22,7 @@
<string name="share">Share</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="copy_password">Copy password into clipboard</string>
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
<string name="open_url">Open url</string>
<string name="cannot_resolve_action">No application can open that</string>
@@ -36,6 +37,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>
@@ -46,9 +48,11 @@
<string name="use_history">Save scan history</string>
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="copy_immediately">Automatically copy contents into clipboard</string>
<string name="show_meta_data">Show metadata</string>
<string name="show_hex_dump">Show hex dump</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="send_scan_url">Send every scan to URL</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
@@ -83,6 +87,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;
}
+1 -1
View File
@@ -1,7 +1,7 @@
buildscript {
ext {
kotlin_version = '1.3.72'
tools_version = '3.6.3'
tools_version = '4.0.0'
build_tools_version = '29.0.3'
sdk_version = 29
support_version = '25.3.1'
@@ -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
@@ -0,0 +1,7 @@
* 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
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue May 07 14:37:45 CEST 2019
#Sun May 31 13:11:56 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
Vendored
+4
View File
@@ -1,5 +1,9 @@
#!/usr/bin/env sh
# ensure this app builds *without* the NDK because using the NDK will
# produce broken for Android 6
export ANDROID_NDK_HOME=
##############################################################################
##
## Gradle start up script for UN*X

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg height="48px" viewBox="0 0 48 48" width="48px" xmlns="http://www.w3.org/2000/svg">
<circle style="fill: rgb(182, 212, 111);" cx="24" cy="24" r="24"/>
<path d="M 29 27 L 31 27 L 31 19 C 31 17.9 30.1 17 29 17 L 21 17 L 21 19 L 29 19 L 29 27 Z M 19 29 L 19 13 L 17 13 L 17 17 L 13 17 L 13 19 L 17 19 L 17 29 C 17 30.1 17.9 31 19 31 L 29 31 L 29 35 L 31 35 L 31 31 L 35 31 L 35 29 L 19 29 Z" style="fill: rgb(255, 255, 255);"/>
</svg>

Before

Width:  |  Height:  |  Size: 478 B

+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