Compare commits

...
28 Commits
Author SHA1 Message Date
Markus Fisch 67471ee141 Advance version number to 1.22.3 2020-03-12 10:07:30 +01:00
Markus Fisch 6999f0a863 Advance version number to 1.22.2 2020-03-11 21:52:33 +01:00
Markus Fisch 6574a5d651 Fix getting window insets for Lollipop and above
Don't use the static `windowInsets` when `setWindowInsetListener()`
is called because this is just for pre-Lollipop devices where
`ViewCompat.setOnApplyWindowInsetsListener(view)` *never* fires.
2020-03-11 21:42:55 +01:00
Markus Fisch 1639e73738 Advance version number to 1.22.1 2020-03-10 19:32:06 +01:00
Markus Fisch 68fa67f54f Save non-printable content as raw data 2020-03-09 17:05:42 +01:00
Markus Fisch 326c34b6c1 Clean up some style issues 2020-03-09 17:04:31 +01:00
Markus Fisch a43dca3644 Advance version number to 1.22.0 2020-03-04 21:51:06 +01:00
Markus Fisch 168ccf7d10 Remove using a special tool version for Catalina
But keep using a special version for `buildToolsVersion` since
RenderScript requires a minimum of 29.0.3 on Catalina what
doesn't work on Linux yet.
2020-03-02 13:59:23 +01:00
Markus Fisch a9630b4d53 Optimize imports 2020-02-28 21:58:59 +01:00
Markus Fisch 4a138bd425 Highlight detected barcodes in camera view too
Although it's just visible before the result screen
takes over.
2020-02-28 21:48:54 +01:00
Markus Fisch 339ff59270 Update build tools 2020-02-27 22:22:21 +01:00
yzqzssandGitHub d208ec12bc Add bing and yandex search engines 2020-02-23 14:06:54 +01:00
yzqzssandGitHub 893ddefa71 Update Chinese Translation 2020-02-22 19:35:36 +01:00
Markus Fisch 5d56365e79 Skip forceCompat() when building on Catalina
Calling RenderScript.forceCompat() on a build that was made with the
new build tools (which are required to make RenderScript build again
on macOS Catalina), results in a RSRuntimeException (Loading of ScriptC
script failed).

RenderScript.forceCompat() was introduced to fix RenderScript for
Lineage 16.0. Maybe this is no longer required with those new build
tools?

Yes, this means this setup is producing platform independent builds
that have different features depending on what platform they were
built on. And yes, that's as bad as it sounds.

So, for the time being, it's probably better to _NOT_ use Catalina
to make release builds.
2020-02-20 15:08:52 +01:00
Markus Fisch 3237467503 Check for macOS Catalina and use appropriate tools
Since RenderScript can no longer be compiled on macOS Catalina
because Catalina cannot run 32bit binaries and Google failed
to provide a 64bit compiler in time. Now, as if that wasn't
bad enough, the new build tools that provide a 64bit compiler
don't work on any other system than macOS unfortunately.

For details on the issue see:
https://issuetracker.google.com/issues/142590626
2020-02-20 15:08:06 +01:00
Markus Fisch 346529469c Revert "Update build tools for macOS Catalina"
This reverts commit cf4020c613.

Those new build tools don't work on other systems than macOS
Catalina and even there produce broken builds :(

Also, it looks like the only solution is to remove
`RenderScript.forceCompat()` what's probably no longer
required to support Lineage 16.0 with those new build tools.
Confirmation pending.
2020-02-18 16:23:39 +01:00
zmniandGitHub 9f4e420dbb Update Indonesian translation 2020-02-17 15:09:33 +01:00
Markus Fisch cf4020c613 Update build tools for macOS Catalina
To make RenderScript compile again on Catalina.

For details see:
https://issuetracker.google.com/issues/142590626
2020-02-17 10:43:10 +01:00
Markus Fisch 93edde7ed1 Specify full backup rules
"true" isn't a valid value for android:fullBackupContent.
2020-02-17 10:36:19 +01:00
unbranchedandGitHub 362e462c2a Update Italian translation 2020-02-09 18:51:08 +01:00
Markus Fisch a7241bc0fb Fix typo in remark about commit messages 2020-02-08 12:43:57 +01:00
Markus Fisch f26046f34b Add links for all supported barcode formats
And put them into a list.
2020-02-08 12:39:44 +01:00
zmniandMarkus Fisch a70813ea5c Update Indonesian translation 2020-01-12 13:46:32 +01:00
Markus Fisch 3f251fdb66 Advance version number to 1.21.0 2019-12-29 17:03:28 +01:00
Markus Fisch b76cf329dd Ignore PickActivity for Firebase App Indexing
Because Lint/Firebase can't handle the VIEW intent filter
without a http URL. What doesn't make any sense here.
2019-12-29 17:00:18 +01:00
Markus Fisch e0ad177a62 Improve loading big images
Load a scaled down bitmap instead of trying to load the
whole image and then scaling it down. If the image is too
large, it cannot be loaded completely in the first place.
2019-12-29 13:57:35 +01:00
Markus Fisch 1223bd3679 Remove file scheme for VIEW intent
Because newer Androids use the "content://" scheme instead of the
"file://" scheme. So remove this restriction.
2019-12-29 12:44:33 +01:00
Markus Fisch b4e24fa328 Save and restore history list state
If we're going back to the list. Makes it far easier to
look through a long list.
2019-12-25 18:14:08 +01:00
27 changed files with 408 additions and 168 deletions
+21
View File
@@ -1,5 +1,26 @@
# Change Log
# 1.22.3
* Fix broken RenderScript built
# 1.22.2
* Fix getting window insets for Android Lollipop and above
# 1.22.1
* Ensure non-printable content is saved as raw data
# 1.22.0
* Highlight detected barcodes in camera view
* Add bing and yandex search engines
* Update Chinese Translation
* Update Italian translation
* Update Indonesian translation
# 1.21.0
* Save and restore history list state
* Improve scanning from large images
* Fix opening image files by "Open with"
# 1.20.1
* Fix window insets below Android Lollipop
+1 -1
View File
@@ -13,7 +13,7 @@ to squash when merging into master:
$ git merge cool_feature --squash
Then write a [good commit messages][commit_messages] to keep the history
Then write a [good commit message][commit_messages] to keep the history
meaningful and useful. One feature, one commit.
[android_best_practices]: https://developer.android.com/distribute/best-practices/develop/
+55 -11
View File
@@ -2,23 +2,51 @@
Yet another barcode scanner for Android. As if there weren't [enough][play].
This one is free, without any ads and completely open source.
This one is free, without any ads and open source.
Works in portrait and landscape orientation, can read inverted codes,
comes in Material Design and can also generate barcodes.
Uses the [ZXing][zxing] ("Zebra Crossing") barcode scanning library that
supports the following barcode formats for reading:
AZTEC, CODABAR, CODE 39, CODE 93, CODE 128, DATA MATRIX, EAN 8, EAN 13, ITF,
MAXICODE, PDF417, QR CODE, RSS 14, RSS EXPANDED, UPC A, UPC E and
UPC EAN EXTENSION.
Binary Eye uses the [ZXing][zxing] ("Zebra Crossing") barcode scanning
library.
And those barcodes formats can be generated with ZXing:
AZTEC, CODABAR, CODE 39, CODE 128, DATA MATRIX, EAN 8, EAN 13, ITF, PDF 417,
QR CODE and UPC A.
## Supported Barcode Formats
And it's written in [Kotlin][kotlin] because I wanted to explore that a
bit more.
### Read
ZXing can _read_ the following barcode formats:
* [AZTEC][aztec]
* [CODABAR][codabar]
* [CODE 39][code_39]
* [CODE 93][code_93]
* [CODE 128][code_128]
* [DATA MATRIX][data_matrix]
* [EAN 8][ean_8]
* [EAN 13][ean_13]
* [ITF][itf]
* [MAXICODE][maxicode]
* [PDF417][pdf417]
* [QR CODE][qr_code]
* [RSS 14][rss]
* [RSS EXPANDED][rss]
* [UPC A][upc_a]
* [UPC E][upc_e]
* [UPC EAN EXTENSION][upc_ean]
### Generate
ZXing can _generate_ the following barcode formats:
* [AZTEC][aztec]
* [CODABAR][codabar]
* [CODE 39][code_39]
* [CODE 128][code_128]
* [DATA MATRIX][data_matrix]
* [EAN 8][ean_8]
* [EAN 13][ean_13]
* [ITF][itf]
* [PDF 417][pdf417]
* [QR CODE][qr_code]
* [UPC A][upc_a]
## Screenshots
@@ -39,3 +67,19 @@ bit more.
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
[zxing]: https://github.com/zxing/zxing
[kotlin]: http://kotlinlang.org/
[aztec]: https://en.wikipedia.org/wiki/Aztec_Code
[codabar]: https://en.wikipedia.org/wiki/Codabar
[code_39]: https://en.wikipedia.org/wiki/Code_39
[code_93]: https://en.wikipedia.org/wiki/Code_93
[code_128]: https://en.wikipedia.org/wiki/Code_128
[data_matrix]: https://en.wikipedia.org/wiki/Data_Matrix
[ean_8]: https://en.wikipedia.org/wiki/EAN-8
[ean_13]: https://en.wikipedia.org/wiki/International_Article_Number
[itf]: https://en.wikipedia.org/wiki/Interleaved_2_of_5
[maxicode]: https://en.wikipedia.org/wiki/MaxiCode
[pdf417]: https://en.wikipedia.org/wiki/PDF417
[qr_code]: https://en.wikipedia.org/wiki/QR_code
[rss]: https://en.wikipedia.org/wiki/GS1_DataBar
[upc_a]: https://en.wikipedia.org/wiki/Universal_Product_Code
[upc_e]: https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
[upc_ean]: https://en.wikipedia.org/wiki/Universal_Product_Code#EAN-13
+4 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 50
versionName '1.20.1'
versionCode 55
versionName '1.22.3'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -18,6 +18,8 @@ android {
renderscriptSupportModeEnabled true
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
buildConfigField 'boolean', 'IS_CATALINA', is_catalina.toString()
}
signingConfigs {
+4 -3
View File
@@ -16,7 +16,7 @@
tools:ignore="UnusedAttribute"
android:name=".app.BinaryEyeApp"
android:allowBackup="true"
android:fullBackupContent="true"
android:fullBackupContent="@xml/backup_rules"
android:supportsRtl="true"
android:resizeableActivity="true"
android:label="@string/app_name"
@@ -55,7 +55,8 @@
</activity>
<activity
android:name=".activity.PickActivity"
android:label="@string/pick_code_to_scan">
android:label="@string/pick_code_to_scan"
tools:ignore="AppLinkUrlError">
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -64,7 +65,7 @@
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*" android:scheme="file"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
<activity
@@ -22,7 +22,10 @@ import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasCameraPermission
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.graphics.Mapping
import de.markusfisch.android.binaryeye.graphics.frameToView
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.widget.DetectorView
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.cameraview.widget.CameraView
import kotlinx.coroutines.GlobalScope
@@ -33,6 +36,7 @@ class CameraActivity : AppCompatActivity() {
private lateinit var vibrator: Vibrator
private lateinit var cameraView: CameraView
private lateinit var detectorView: DetectorView
private lateinit var zoomBar: SeekBar
private lateinit var flashFab: View
@@ -83,12 +87,13 @@ class CameraActivity : AppCompatActivity() {
super.onCreate(state)
setContentView(R.layout.activity_camera)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
initSystemBars(this)
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
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() }
@@ -264,6 +269,7 @@ class CameraActivity : AppCompatActivity() {
val frameWidth = cameraView.frameWidth
val frameHeight = cameraView.frameHeight
val frameOrientation = cameraView.frameOrientation
var mapping: Mapping? = null
var decoding = true
camera.setPreviewCallback { frameData, _ ->
if (decoding) {
@@ -274,7 +280,19 @@ class CameraActivity : AppCompatActivity() {
frameOrientation
)
result?.let {
preprocessor?.let {
mapping = mapping ?: frameToView(
it.outWidth,
it.outHeight,
cameraView.previewRect
)
}
cameraView.post {
mapping?.let {
detectorView.mark(
it.map(result.resultPoints)
)
}
vibrator.vibrate(100)
showResult(
this@CameraActivity,
@@ -1,12 +1,14 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.MediaStore
import android.os.Vibrator
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
@@ -19,22 +21,23 @@ import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setWindowInsetListener
import de.markusfisch.android.binaryeye.app.setupInsets
import de.markusfisch.android.binaryeye.graphics.crop
import de.markusfisch.android.binaryeye.graphics.downsizeIfBigger
import de.markusfisch.android.binaryeye.graphics.loadImageUri
import de.markusfisch.android.binaryeye.graphics.mapResult
import de.markusfisch.android.binaryeye.widget.CropImageView
import de.markusfisch.android.binaryeye.zxing.Zxing
import java.io.IOException
import kotlin.math.max
import kotlin.math.min
class PickActivity : AppCompatActivity() {
private val zxing = Zxing()
private lateinit var vibrator: Vibrator
private lateinit var cropImageView: CropImageView
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_pick)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
initSystemBars(this)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setupInsets(findViewById(android.R.id.content), toolbar)
@@ -69,7 +72,7 @@ class PickActivity : AppCompatActivity() {
}
val scannedRect = Rect()
val scanBounds: () -> Result? = {
val scanWithinBounds: () -> Result? = {
var result: Result? = null
crop(
bitmap,
@@ -85,38 +88,24 @@ class PickActivity : AppCompatActivity() {
cropImageView = findViewById(R.id.image) as CropImageView
cropImageView.setImageBitmap(bitmap)
cropImageView.onScan = {
val rect = Rect()
scanBounds()?.also {
rect.left = Int.MAX_VALUE
rect.top = Int.MAX_VALUE
for (rp in it.resultPoints) {
val x = rp.x.toInt()
val y = rp.y.toInt()
rect.left = min(rect.left, x)
rect.right = max(rect.right, x)
rect.top = min(rect.top, y)
rect.bottom = max(rect.bottom, y)
}
// map result points onto view
val bounds = cropImageView.getBoundsRect()
val fx = bounds.width() / scannedRect.width().toFloat()
val fy = bounds.height() / scannedRect.height().toFloat()
rect.set(
(rect.left * fx).toInt(),
(rect.top * fy).toInt(),
(rect.right * fx).toInt(),
(rect.bottom * fx).toInt()
var points: List<Point>? = null
scanWithinBounds()?.let {
points = mapResult(
scannedRect.width(),
scannedRect.height(),
cropImageView.getBoundsRect(),
it
)
rect.offset(bounds.left.toInt(), bounds.top.toInt())
vibrator.vibrate(100)
}
rect
points
}
setWindowInsetListener { insets ->
cropImageView.windowInsets.set(insets)
}
findViewById(R.id.scan).setOnClickListener {
scanImage(scanBounds())
scanImage(scanWithinBounds())
}
}
@@ -143,21 +132,12 @@ class PickActivity : AppCompatActivity() {
val uri = intent.getParcelableExtra<Parcelable>(
Intent.EXTRA_STREAM
) as? Uri ?: return null
return loadImageUri(uri)
return loadImageUri(contentResolver, uri)
}
private fun loadImageToOpen(intent: Intent): Bitmap? {
val uri = intent.data ?: return null
return loadImageUri(uri)
}
private fun loadImageUri(uri: Uri): Bitmap? = try {
downsizeIfBigger(
MediaStore.Images.Media.getBitmap(contentResolver, uri),
1024
)
} catch (e: IOException) {
null
return loadImageUri(contentResolver, uri)
}
private fun scanImage(result: Result?) {
@@ -20,11 +20,9 @@ class ScansAdapter(context: Context, cursor: Cursor) :
context: Context,
cursor: Cursor,
parent: ViewGroup
): View {
return LayoutInflater.from(parent.context).inflate(
R.layout.row_scan, parent, false
)
}
) = LayoutInflater.from(parent.context).inflate(
R.layout.row_scan, parent, false
)
override fun bindView(
view: View,
@@ -2,6 +2,7 @@ package de.markusfisch.android.binaryeye.app
import android.app.Application
import android.support.v8.renderscript.RenderScript
import de.markusfisch.android.binaryeye.BuildConfig
import de.markusfisch.android.binaryeye.preference.Preferences
import de.markusfisch.android.binaryeye.repository.DatabaseRepository
@@ -12,9 +13,19 @@ class BinaryEyeApp : Application() {
override fun onCreate() {
super.onCreate()
// required to make RenderScript work for Lineage 16.0
// possibly because of a system/device bug
RenderScript.forceCompat()
// Since RenderScript can no longer be compiled on macOS Catalina
// because Catalina cannot run 32bit binaries and Google failed
// to provide a 64bit compiler in time. Now, as if that wasn't
// bad enough, `RenderScript.forceCompat()` makes any build compiled
// with the new build tools that provide a 64bit compiler crash
// so we need to exclude this if we're building on Catalina.
// Maybe `RenderScript.forceCompat()` isn't required anymore for
// those new builds but I don't have a device to test this.
if (!BuildConfig.IS_CATALINA) {
// required to make RenderScript work for Lineage 16.0
// possibly because of a system/device bug
RenderScript.forceCompat()
}
db.open(this)
prefs.init(this)
@@ -82,8 +82,9 @@ private fun getAppCompatActivity(context: Context): AppCompatActivity? {
}
private var windowInsetsListener: ((insets: Rect) -> Unit)? = null
// invoke this from Fragment.onCreateView() *or* Activity.onCreate()
fun setWindowInsetListener(listener: (insets: Rect) -> Unit) {
if (windowInsets.top > 0) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
listener(windowInsets)
} else {
windowInsetsListener = listener
@@ -91,14 +92,18 @@ fun setWindowInsetListener(listener: (insets: Rect) -> Unit) {
}
private val windowInsets = Rect()
// invoke this from Activity.onCreate() to setup listening for insets
fun setupInsets(view: View, toolbar: Toolbar) {
val toolBarHeight = toolbar.layoutParams.height
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// save insets so setWindowInsetListener() can return them
// when it's called from Fragment.onCreateView() later on
windowInsets.set(0, toolBarHeight, 0, 0)
windowInsetsListener?.also { it(windowInsets) }
return
}
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
// runs *after* layout and thus *after* Fragment.onCreateView()
if (insets.hasSystemWindowInsets()) {
windowInsets.set(
insets.systemWindowInsetLeft,
@@ -59,9 +59,9 @@ class Database {
timestamp
).toString()
)
if (hasNonPrintableCharacters(content) && raw != null) {
if (hasNonPrintableCharacters(content)) {
cv.put(SCANS_CONTENT, "")
cv.put(SCANS_RAW, raw)
cv.put(SCANS_RAW, if (raw != null) raw else content.toByteArray())
} else {
cv.put(SCANS_CONTENT, content)
}
@@ -3,12 +3,7 @@ package de.markusfisch.android.binaryeye.fragment
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.*
import com.google.zxing.BarcodeFormat
@@ -3,6 +3,7 @@ package de.markusfisch.android.binaryeye.fragment
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.support.annotation.WorkerThread
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
@@ -27,10 +28,12 @@ class HistoryFragment : Fragment() {
private lateinit var fab: View
private lateinit var progressView: View
private var scansAdapter: ScansAdapter? = null
private val parentJob = Job()
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
private var scansAdapter: ScansAdapter? = null
private var listViewState: Parcelable? = null
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
@@ -96,6 +99,11 @@ class HistoryFragment : Fragment() {
update(context)
}
override fun onPause() {
super.onPause()
listViewState = listView.onSaveInstanceState()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_history, menu)
}
@@ -137,6 +145,9 @@ class HistoryFragment : Fragment() {
scansAdapter?.also { it.changeCursor(null) }
scansAdapter = ScansAdapter(context, it)
listView.adapter = scansAdapter
listViewState?.also {
listView.onRestoreInstanceState(it)
}
}
}
}
@@ -1,28 +1,52 @@
package de.markusfisch.android.binaryeye.graphics
import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.RectF
import kotlin.math.max
import android.net.Uri
import java.io.IOException
import kotlin.math.roundToInt
fun downsizeIfBigger(bitmap: Bitmap, maxSize: Int): Bitmap = if (
max(bitmap.width, bitmap.height) > maxSize
) {
val srcWidth = bitmap.width
val srcHeight = bitmap.height
var newWidth = maxSize
var newHeight = maxSize
if (srcWidth > srcHeight) {
newHeight = (newWidth.toFloat() / srcWidth.toFloat() *
srcHeight.toFloat()).roundToInt()
} else {
newWidth = (newHeight.toFloat() / srcHeight.toFloat() *
srcWidth.toFloat()).roundToInt()
fun loadImageUri(cr: ContentResolver, uri: Uri): Bitmap? = try {
val options = BitmapFactory.Options()
cr.openInputStream(uri)?.use {
options.inJustDecodeBounds = true
BitmapFactory.decodeStream(it, null, options)
options.inSampleSize = calculateInSampleSize(
options.outWidth,
options.outHeight,
1024,
1024
)
options.inJustDecodeBounds = false
}
Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
} else {
bitmap
cr.openInputStream(uri)?.use {
BitmapFactory.decodeStream(it, null, options)
}
} catch (e: IOException) {
null
}
private fun calculateInSampleSize(
width: Int,
height: Int,
reqWidth: Int,
reqHeight: Int
): Int {
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (
halfHeight / inSampleSize >= reqHeight ||
halfWidth / inSampleSize >= reqWidth
) {
inSampleSize *= 2
}
}
return inSampleSize
}
fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
@@ -0,0 +1,66 @@
package de.markusfisch.android.binaryeye.graphics
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Point
import android.graphics.Rect
import android.support.v4.content.ContextCompat
import com.google.zxing.Result
import com.google.zxing.ResultPoint
import de.markusfisch.android.binaryeye.R
import kotlin.math.roundToInt
class Candidates(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.style = Paint.Style.FILL
}
fun draw(canvas: Canvas, points: List<Point>) {
for (point in points) {
canvas.drawCircle(
point.x.toFloat(),
point.y.toFloat(),
radius,
paint
)
}
}
}
fun mapResult(
width: Int,
height: Int,
viewRect: Rect,
result: Result
): List<Point> = frameToView(width, height, viewRect).map(result.resultPoints)
fun frameToView(width: Int, height: Int, viewRect: Rect) = Mapping(
viewRect.width().toFloat() / width.toFloat(),
viewRect.height().toFloat() / height.toFloat(),
viewRect.left,
viewRect.top
)
data class Mapping(
val ratioX: Float,
val ratioY: Float,
val offsetX: Int,
val offsetY: Int
) {
fun map(points: Array<ResultPoint>): List<Point> {
return points.map {
Point(
(it.x * ratioX).roundToInt() + offsetX,
(it.y * ratioY).roundToInt() + offsetY
)
}
}
}
@@ -5,36 +5,34 @@ import android.graphics.*
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.graphics.Candidates
import kotlin.math.roundToInt
class CropImageView(context: Context, attr: AttributeSet) :
ConfinedScalingImageView(context, attr) {
val windowInsets = Rect()
var onScan: (() -> Rect)? = null
var onScan: (() -> List<Point>?)? = null
private val candidates = Candidates(context)
private val boundsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val candidatePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val lastMappedRect = RectF()
private val dp = context.resources.displayMetrics.density
private val padding: Int = (dp * 24f).roundToInt()
private val onScanRunnable = Runnable {
onScan?.invoke()?.also {
candidateRect = it
candidatePoints = it
invalidate()
}
}
private var candidateRect: Rect? = null
private var candidatePoints: 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)
candidatePaint.color = ContextCompat.getColor(context, R.color.candidate)
candidatePaint.style = Paint.Style.FILL_AND_STROKE
candidatePaint.strokeWidth = dp * 3f
scaleType = ScaleType.CENTER_CROP
}
@@ -54,9 +52,12 @@ class CropImageView(context: Context, attr: AttributeSet) :
)
}
fun getBoundsRect(): RectF {
return bounds
}
fun getBoundsRect() = Rect(
bounds.left.roundToInt(),
bounds.top.roundToInt(),
bounds.right.roundToInt(),
bounds.bottom.roundToInt()
)
private fun setBoundsWithPadding(
left: Int,
@@ -84,11 +85,10 @@ class CropImageView(context: Context, attr: AttributeSet) :
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(bounds, boundsPaint)
val rc = candidateRect
if (rc != null && rc.width() > 0) {
canvas.drawRect(rc, candidatePaint)
candidateRect = null
candidatePoints?.let {
candidates.draw(canvas, it)
}
candidatePoints = null
val mr = mappedRect ?: return
if (mr != lastMappedRect) {
removeCallbacks(onScanRunnable)
@@ -0,0 +1,36 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.PorterDuff
import android.util.AttributeSet
import android.view.View
import de.markusfisch.android.binaryeye.graphics.Candidates
class DetectorView : View {
private val candidates = Candidates(context)
private var marks: List<Point>? = null
constructor(context: Context, attrs: AttributeSet) :
super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
fun mark(points: List<Point>) {
marks = points
invalidate()
postDelayed({
marks = null
invalidate()
}, 500)
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
marks?.let {
candidates.draw(canvas, it)
}
}
}
@@ -6,7 +6,7 @@ import com.google.zxing.common.HybridBinarizer
import java.util.*
class Zxing {
private val multiFormatReader: MultiFormatReader = MultiFormatReader()
private val multiFormatReader = MultiFormatReader()
init {
val decodeFormats = EnumSet.noneOf<BarcodeFormat>(
@@ -63,10 +63,11 @@ class Zxing {
fun decodePositiveNegative(bitmap: Bitmap): Result? {
val result = decode(bitmap, false)
if (result != null) {
return result
return if (result != null) {
result
} else {
decode(bitmap, true)
}
return decode(bitmap, true)
}
fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
@@ -86,8 +87,10 @@ class Zxing {
} else {
bitmap
}.getPixels(pixels, 0, width, 0, 0, width, height)
val source = RGBLuminanceSource(width, height, pixels)
return decodeLuminanceSource(source, invert)
return decodeLuminanceSource(
RGBLuminanceSource(width, height, pixels),
invert
)
}
private fun decodeLuminanceSource(
@@ -3,6 +3,10 @@
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<de.markusfisch.android.binaryeye.widget.DetectorView
android:id="@+id/detector_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
+7 -7
View File
@@ -5,10 +5,10 @@
<string name="compose_barcode">Buat barcode</string>
<string name="decode_barcode">Dekode barcode</string>
<string name="content">Konten</string>
<string name="binary_data">(data binari)</string>
<string name="binary_data">(data biner)</string>
<plurals name="barcode_info">
<item quantity="one">%2$d karakter, %1$s</item>
<item quantity="other">%2$d karakter, %1$s</item>
<item quantity="one">%1$s, %2$d karakter</item>
<item quantity="other">%1$s, %2$d karakter</item>
</plurals>
<string name="toggle_flash">Nyala/Matikan blitz</string>
<string name="share">Bagikan</string>
@@ -63,11 +63,11 @@
<string name="csv_delimiter">Karakter pemisah apa yang harus digunakan (Berkas-CSV)?</string>
<string name="delimiter_comma">Koma</string>
<string name="delimiter_semicolon">Titik koma</string>
<string name="csv_allow_binary">Izinkan data binari di dalam berkas keluaran?</string>
<string name="csv_allow_binary">Izinkan data biner di dalam berkas keluaran?</string>
<string name="no">Tidak</string>
<string name="csv_allow_binary_message">Anda sebaiknya tidak mengizinkannya, karena akan merusak format berkas dan kemungkinan tidak bisa lagi dibaca oleh aplikasi penyunting teks.</string>
<string name="saved_in_downloads">Simpan di dalam unduhan</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
<string name="rotate_image_cw">Putar gambar searah jarum jam</string>
<string name="pick_file">Pilih berkas gambar</string>
<string name="pick_code_to_scan">Pilih kode untuk dipindai</string>
</resources>
+17 -17
View File
@@ -1,7 +1,7 @@
<resources>
<string name="no_camera_no_fun">Quest\'app non ha alcuna utilità senza accesso alla camera. Addio.</string>
<string name="camera_error">Impossibile accedere alla camera. Riprovare</string>
<string name="scan_code">Codice</string>
<string name="no_camera_no_fun">Quest\'app non ha alcun senso senza l\'accesso alla fotocamera. Ciao.</string>
<string name="camera_error">Impossibile accedere alla fotocamera, riprova</string>
<string name="scan_code">Scansiona codice</string>
<string name="compose_barcode">Crea codice a barre</string>
<string name="decode_barcode">Decodifica codice a barre</string>
<string name="content">Contenuto</string>
@@ -20,44 +20,44 @@
<string name="format">Formato</string>
<string name="size">Dimensioni in pixel</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Contenuto input qui</string>
<string name="input_content_here">Inserisci il contenuto qui</string>
<string name="encode">CODIFICA</string>
<string name="error_no_content">Contenuto mancante</string>
<string name="error_encoding_barcode">Il codice a barre non può essere generato</string>
<string name="view_barcode">Vedi codice a barre</string>
<string name="info">Info</string>
<string name="switch_camera">Cambia camera</string>
<string name="switch_camera">Cambia fotocamera</string>
<string name="history">Cronologia</string>
<string name="preferences">Preferences</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="preferences">Preferenze</string>
<string name="open_immediately">Salta l\'analisi e apri i contenuti direttamente</string>
<string name="use_history">Salva cronologia delle scansioni</string>
<string name="open_with_url">Apri dati sconosciuti con 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="clear_history">Pulisci cronologia</string>
<string name="no_barcode_found">Nessun codice a barre trovato</string>
<string name="pick_list_separator">Come separare elementi di una lista?</string>
<string name="pick_list_separator">Come separare elementi della lista?</string>
<string name="separator_line_break">Interruzione di linea</string>
<string name="separator_ruler">Righello</string>
<string name="save_as_file_name">Salvare come file?</string>
<string name="file_name">Nome del file</string>
<string name="error_saving_binary_data">Impossibile salvare il file</string>
<string name="error_file_exists">Il File esiste già</string>
<string name="connect_to_wifi">Connecti al WiFi</string>
<string name="error_file_exists">Il file esiste già</string>
<string name="connect_to_wifi">Connetti al WiFi</string>
<string name="wifi_config_failed">Impossibile configurare il WiFi</string>
<string name="wifi_added">WiFi aggiunto</string>
<string name="sms_send">Invia SMS</string>
<string name="sms_error">Impossibile inviare SMS</string>
<string name="tel_dial">Componi numero</string>
<string name="tel_error">Impossibile comporre il numero</string>
<string name="mail_send">Invia mail</string>
<string name="mail_error">Impossibile inviare la mail</string>
<string name="mail_send">Invia email</string>
<string name="mail_error">Impossibile inviare l\'email</string>
<string name="vcard_add">Aggiungi ai contatti</string>
<string name="vcard_failed">Impossibile aggiungere ai contatti</string>
<string name="vevent_add">Aggiungi al calendario</string>
<string name="vevent_failed">Impossibile aggiungere al calendario</string>
<string name="otpauth_add">Aggiungi 2FA</string>
<string name="otpauth_error">Impossibile aggiungre il 2FA</string>
<string name="otpauth_error">Impossibile aggiungre la 2FA</string>
<string name="search_web">Cerca nel web</string>
<string name="export_to_file">Esporta su file</string>
<string name="csv_delimiter">Quale delimitatore usare (File-CSV)?</string>
@@ -65,9 +65,9 @@
<string name="delimiter_semicolon">Punto e virgola</string>
<string name="csv_allow_binary">Permetti dati binari nel file di output?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">È consigliato non permetterlo, perché rompe il formato del file e probabilmente lo renderà non più leggibile dagli editor di testo.</string>
<string name="csv_allow_binary_message">È consigliato non permetterlo, perché rovina il formato del file e probabilmente lo renderà non più leggibile dagli editor di testo.</string>
<string name="saved_in_downloads">Salvato nei download</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
<string name="rotate_image_cw">Ruota immagine in senso orario</string>
<string name="pick_file">Scegli file immagine</string>
<string name="pick_code_to_scan">Scegli codice da scansionare</string>
</resources>
+19 -19
View File
@@ -1,7 +1,7 @@
<resources>
<string name="no_camera_no_fun">没有相机权限,这个软件有啥意义。再见!</string>
<string name="no_camera_no_fun">没有给我相机权限,扫个锤子。再见!</string>
<string name="camera_error">无法访问相机,请重试。</string>
<string name="scan_code">Scan code</string>
<string name="scan_code">扫描</string>
<string name="compose_barcode">制作条形码</string>
<string name="decode_barcode">解码条形码</string>
<string name="content">内容</string>
@@ -10,12 +10,12 @@
<item quantity="one">%2$d 字符, %1$s</item>
<item quantity="other">%2$d 字符, %1$s</item>
</plurals>
<string name="toggle_flash">切换闪光灯</string>
<string name="toggle_flash">开关闪光灯</string>
<string name="share">分享</string>
<string name="copy_to_clipboard">复制到剪切板</string>
<string name="put_into_clipboard">复制到剪切板</string>
<string name="open_url">打开链接</string>
<string name="cannot_resolve_action">没有应用程序可以打开它</string>
<string name="cannot_resolve_action">没有相关APP可以打开它</string>
<string name="pick_search_engine">选择一个搜索引擎</string>
<string name="format">编码格式</string>
<string name="size">图片尺寸</string>
@@ -29,7 +29,7 @@
<string name="switch_camera">切换摄像头</string>
<string name="history">历史</string>
<string name="preferences">首选项</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="open_immediately">跳过内容展示并立即打开</string>
<string name="use_history">保存扫描历史</string>
<string name="open_with_url">使用URL打开未知数据</string>
<string name="really_remove_scan">确定删除记录?</string>
@@ -56,18 +56,18 @@
<string name="vcard_failed">无法添加到通讯录</string>
<string name="vevent_add">添加到日历</string>
<string name="vevent_failed">无法添加到日历</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
<string name="otpauth_add">添加两步验证</string>
<string name="otpauth_error">无法添加两步验证</string>
<string name="search_web">在互联网中搜索</string>
<string name="export_to_file">导出为CSV文件</string>
<string name="csv_delimiter">使用哪个分隔符 (CSV-文件)?</string>
<string name="delimiter_comma">逗号</string>
<string name="delimiter_semicolon">分号</string>
<string name="csv_allow_binary">允许输出文件中的二进制数据?</string>
<string name="no"></string>
<string name="csv_allow_binary_message">您可能不想这样做,因为它会破坏文件格式,并且可能无法再由文本编辑器读取。</string>
<string name="saved_in_downloads">已保存到下载目录</string>
<string name="rotate_image_cw">旋转图片</string>
<string name="pick_file">选择图片文件</string>
<string name="pick_code_to_scan">选择扫描的编码</string>
</resources>
@@ -3,6 +3,8 @@
<item>Google</item>
<item>DuckDuckGo</item>
<item>Qwant</item>
<item>Bing</item>
<item>Yandex</item>
<item>OpenFoodFacts.org</item>
<item>OpenBeautyFacts.org</item>
<item>OpenPetFoodFacts.org</item>
@@ -11,6 +13,8 @@
<item>https://www.google.com/search?q=</item>
<item>https://duckduckgo.com/?q=</item>
<item>https://www.qwant.com/?q=</item>
<item>https://bing.com/?q=</item>
<item>https://yandex.com/?q=</item>
<item>https://world.openfoodfacts.org/cgi/search.pl?search_terms=</item>
<item>https://world.openbeautyfacts.org/cgi/search.pl?search_terms=</item>
<item>https://world.openpetfoodfacts.org/cgi/search.pl?search_terms=</item>
+3
View File
@@ -0,0 +1,3 @@
<full-backup-content>
<include domain="root" path="."/>
</full-backup-content>
@@ -1,10 +1,7 @@
package de.markusfisch.android.binaryeye.actions.wifi
import de.markusfisch.android.binaryeye.simpleFail
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.*
import org.junit.Test
class WifiConfigurationFactoryTest {
+22 -5
View File
@@ -1,9 +1,26 @@
buildscript {
ext.kotlin_version = '1.3.61'
ext.tools_version = '3.5.3'
ext.build_tools_version = '28.0.3'
ext.sdk_version = 29
ext.support_version = '25.3.1'
// Since RenderScript can no longer be compiled on macOS Catalina
// because Catalina cannot run 32bit binaries and Google failed
// to provide a 64bit compiler in time. Now, as if that wasn't
// bad enough, the new build tools that provide a 64bit compiler
// don't work on any other system than macOS unfortunately.
ext.is_catalina = {
if (System.properties['os.name'] == 'Mac OS X') {
def numbers = System.properties['os.version'].split('\\.')
if (numbers.length > 1) {
return numbers[1].toInteger() >= 15
}
}
return false
}()
ext {
kotlin_version = '1.3.61'
tools_version = '3.6.1'
build_tools_version = ext.is_catalina ? '29.0.3' : '28.0.3'
sdk_version = 29
support_version = '25.3.1'
}
repositories {
google()
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip