Compare commits

..
55 Commits
Author SHA1 Message Date
Markus Fisch ee0f7c4529 Advance version number to 1.3.2 2017-12-08 22:28:41 +01:00
Markus Fisch 1b296ff83e Update to latest CameraView 2017-12-08 22:26:26 +01:00
Markus Fisch 1a635af7b7 Update to gradle 4 2017-12-08 22:07:05 +01:00
Markus Fisch 568530d470 Use safe calls instead of explicit null checks
Do it the Kotlin way.
2017-12-07 22:01:24 +01:00
Markus Fisch 6248b46fc8 Advance version number to 1.3.1 2017-11-13 21:10:10 +01:00
Markus Fisch 3083d2b625 Change primary colors
Was rejected because the launcher icon was too close to
another app with almost the same primary colors.
2017-11-13 21:03:37 +01:00
Markus Fisch e02420d446 Advance version number to 1.3.0 2017-11-12 21:43:55 +01:00
Markus Fisch e63fe0d601 Save chosen zoom level permanently 2017-11-12 21:42:54 +01:00
Markus Fisch 0803fdcac8 Check Intent.getStringExtra() for null before use 2017-11-12 21:36:05 +01:00
Markus Fisch e64dd2e972 Update to latest Kotlin version 2017-11-12 21:20:38 +01:00
Markus Fisch 418edd34a5 Support tap to focus
Focus that exact spot a user taps on. Helps getting a clear
image on some devices. After 3 seconds the app will return
to the best possible auto focus.
2017-11-12 21:14:51 +01:00
Markus Fisch af2a1dc940 Update CameraView library to latest version 2017-11-12 21:14:27 +01:00
Markus Fisch e0c420c29f Update ZXing to latest version 3.3.2 2017-11-11 14:11:50 +01:00
Markus Fisch 0ba41ca863 Advanced version number to 1.2.0 2017-11-04 22:52:27 +01:00
Markus Fisch ed1826679e Link CI badge with corresponding Travis page 2017-11-04 22:38:07 +01:00
Markus Fisch bd75b5b71d Updated to latest version of CameraView 2017-11-04 22:37:24 +01:00
Markus Fisch bbdf724bbb Update CameraView library to lastest version
Fixes two formal issues some static analyzers will complain
about.
2017-10-28 18:21:06 +02:00
Markus Fisch 0ace08c8b4 Add SonarQube rule to Makefile
To run SonarQube before committing.
2017-10-28 18:07:39 +02:00
Markus Fisch 41164c31f1 Add SonarQube integration again
Reading through the docs it became clear that the secure
token is repository specific so that was the mistake.
2017-10-28 17:54:03 +02:00
Markus Fisch 9f3288e7a8 Revert "Add SonarQube integration"
Somehow SonarQube always says not authorized but the token
is valid and works in another project. May be that SonarQube
cloud allows only one project per organization ...

This reverts commit 26a19a9657.
2017-10-27 09:27:26 +02:00
Markus Fisch a61fc63fa6 Add a Build Passing icon to the README 2017-10-26 23:06:54 +02:00
Markus Fisch 26a19a9657 Add SonarQube integration 2017-10-26 23:05:31 +02:00
Markus Fisch a0d157551a Add Travis CI 2017-10-26 22:59:46 +02:00
Markus Fisch 53fe8b3689 Save zoom level over orientation changes 2017-10-26 22:49:24 +02:00
Markus Fisch 0da9e34e55 Set a default zoom of 1/3 2017-10-26 21:53:12 +02:00
Markus Fisch db276372b7 Enable back arrow for MainActivity first fragments
MainActivity is a subsequent activity and has always a back
arrow now.
2017-10-26 21:52:52 +02:00
Markus Fisch c148635c26 Set a modest wallpaper as window background 2017-10-26 21:50:11 +02:00
Markus Fisch 5c99309125 Call onCameraError() when Camera.open() fails too
And remove the Camera argument from onCameraError() as it
wouldn't make sense when Camera.open() fails.
2017-10-24 22:33:46 +02:00
Markus Fisch 2443eb066a Call Thread.interrupt() after InterruptedException
Explicitly try to interrupt the thread after an
InterruptedException.
2017-10-24 21:57:42 +02:00
Markus Fisch d7281266e4 Add missing permission checking to camera activity
Somehow I forgot to check for the camera permission.
I should pick a device running Marshmallow for testing by now.
2017-10-24 21:54:54 +02:00
Markus Fisch e8c676ab8c Remove marker source graphic
No longer needed.
2017-10-20 21:26:02 +02:00
Markus Fisch 6c1353596d Use a dedicated camera activity
By removing the camera fragment everything gets much
simpler.
2017-10-20 21:18:55 +02:00
Markus Fisch ff14130c91 Put a thin horizontal line over scan camera
Makes it easy to align a barcode with the camera.
2017-10-20 21:18:39 +02:00
Markus Fisch e0147df76c Put flash action into fab
Swap flash with create action.

Originally I put the flash action in the toolbar
because not all devices have a flash. But it makes
more sense to have the flash beside the zoom slider.
2017-10-20 21:18:19 +02:00
Markus Fisch cbc2b6b8db Remove the locking markers
While the animation is nice, they're not very helpful
for actually scanning a barcode. A simple line is
probably a better to choice.
2017-10-20 21:17:59 +02:00
Markus Fisch bc727ab45f Add a zoom slider 2017-10-20 21:17:41 +02:00
Markus Fisch 9e8153e8f6 Update CameraView to 1.1.0
Handle camera errors when getting or setting parameters.
2017-10-18 22:58:36 +02:00
Markus Fisch b9a5687c5a Advanced version number to 1.1.1 2017-09-03 12:35:35 +02:00
Markus Fisch cbc0ff435f Invert every second frame
Forgot to invert every second frame to also read inverted
barcodes while simplifying image processing.
2017-09-03 12:26:49 +02:00
Markus Fisch 3a650bc0cd Advanced version number to 1.1.0 2017-09-03 12:17:48 +02:00
Markus Fisch cbcc9591ff Use only Y (lumo) channel from YUV input
Barcode recognition works on contrast so we don't need
color information and can therefore simplify processing
of input images.
2017-09-02 14:22:26 +02:00
Markus Fisch 7578d9b217 Renamed Compose/Result- to Encode/DecodeFragment
Better names.
2017-09-02 13:45:06 +02:00
Markus Fisch 7789c21a96 Open README on GitHub from action menu
To make everybody aware the app's using ZXing and what
that is and what license it has.
2017-09-02 13:35:01 +02:00
Markus Fisch 366548dc2a Use BarcodeFormat instead of strings
And control size of generated barcodes manually.

Also transfer barcode format from ResultFragment
to ComposeFragment.
2017-09-02 13:32:14 +02:00
Markus Fisch 2324c18807 Renamed FragmentAssistant to content_frame
content_frame.kt doesn't contain a class but a bunch
of functions related to the content_frame layout so
it's probably more clear.
2017-09-02 13:30:39 +02:00
Markus Fisch e7e42694bc Create barcodes from result screen 2017-08-30 20:20:05 +02:00
Markus Fisch 981fb2f0d8 Consider navigation bar on the right side
When laying out lock marks.
2017-08-30 20:07:31 +02:00
Markus Fisch 90036e1ba2 Keep Allocation and Bitmap between frames 2017-08-30 19:59:37 +02:00
Markus Fisch f0916d55ba Have decoded content editable
This way the user may add/remove something before sharing.
2017-08-30 19:47:57 +02:00
Markus Fisch 07177a0c70 Create barcodes too
Extended Zxing wrapper to create barcodes and added
some simple UI for it.
2017-08-30 19:41:08 +02:00
Markus Fisch 478e8930a8 Added a special debug icon
So it's easy to separate the debug from the release build.
2017-08-27 18:47:41 +02:00
Markus Fisch 2d70b64dd1 Advanced version number to 1.0.1 2017-08-27 18:37:44 +02:00
Markus Fisch 25173bd7c7 Keep proguard from messing with renderscript
Otherwise the release build will miss the renderscript
libraries.
2017-08-27 18:35:46 +02:00
Markus Fisch 9adb5174af Added link to Google Play 2017-08-27 17:26:56 +02:00
Markus Fisch 5f0c00cd55 Added a license 2017-08-27 16:30:32 +02:00
81 changed files with 1601 additions and 963 deletions
+18
View File
@@ -0,0 +1,18 @@
language: android
android:
components:
- platform-tools
- build-tools-26.0.2
- android-27
- extra-android-m2repository
addons:
sonarcloud:
organization: "markusfisch-github"
token:
secure: "mTpn1JNKn/PzEFGTRvkmSfCj0c6nX8n/ef2IO0xPSFyveuM+cpNka5d4/nT1QxEqq1sLUj5HMHEShdk6XlER5Oro+DqekTSw8ONv8P24IZGjiM5noLJZ7uqv/73tM0SevROl3Ldee6AzyS7PzcextQms5UC8uyCgI2jbYwz3hpayaWPquBGRnlw7xGuYwCjJS2cMrhz8sEYRn3Egk3no1YJlxCdbIVaqtVmLweqBzLp176FWB5Svg8N3r+UzaZ3fhQn6LhlHPuObPTrCoVjy6W9TxdUsHfW3F61Zei9h1IEsCC0qsalY0OZC8ML5F2DTDp0spKHnPlXR4Q4hF9XIngk2vPVMtfJhu3JJRojjrxhtfOUZO/VS0AMpueZ5QiL1ZbHziKk9ztuRsB+OIoehWnBTq/r/igrWfqxfsq2VURfAYFldRnobBlT/svNkHBjHfZl9EqKprvwmmvw15ZD0twgsskweDn3GssHC5MGTH6+ujZI19t/SLbDMY2KVPNCrG9sHEXSqKT8wWxqGigUaIbVytD2SqGWep0qsB86FQLAXOmgr3+0upTx3B8YhMG5GGu/R45pJBwzXZbTqSx0goaxP+BBEkzcjQHNl0RvpF44m/FbPMYUBuNyHHEoEZC5G1raOV9jtI8G6whQP3OaT8lO2Aqbp1W/lTSEnCDcKYlU="
script:
- bash gradlew build
- bash gradlew sonarqube
branches:
only:
- master
+31
View File
@@ -0,0 +1,31 @@
# Change Log
## 1.3.2
* Fixed crash when going back very fast
## 1.3.1
* Changed primary color
## 1.3.0
* Support tap to focus
* Save chose zoom level permanently
* Update ZXing to latest version (3.3.2)
## 1.2.0
* Added a camera zoom slider
* Added an unobtrusive wallpaper background
* Replaced lock marks with just a horizontal line
* Put toggling torch mode into Floating Action Button
## 1.1.1
* Brought back inverting every second frame
## 1.1.0
* Create barcodes
* Streamlined video pre-processing
* Decoded content is editable now
* Fixed position of lock marks on tablets
* Open README from menu
## 1.0.1
* Keep proguard from messing with renderscript
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Markus Fisch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+4 -1
View File
@@ -1,5 +1,5 @@
PACKAGE = de.markusfisch.android.binaryeye
APK = app/build/outputs/apk/app-debug.apk
APK = app/build/outputs/apk/debug/app-debug.apk
all: debug install start
@@ -16,6 +16,9 @@ release: lint
lint:
./gradlew lintDebug
sonarqube:
./gradlew sonarqube
infer: clean
infer -- ./gradlew assembleDebug
+4
View File
@@ -1,5 +1,7 @@
# Binary Eye
<a href="https://travis-ci.org/markusfisch/BinaryEye"><img src="https://api.travis-ci.org/markusfisch/BinaryEye.svg" alt="Travis-ci"/></a>
Yet another barcode scanner for Android. As if there weren't [enough][play].
This one is totally free, open source and does use the [ZXing][zxing] barcode
@@ -9,6 +11,8 @@ Unlike many others it works in portrait and landscape orientation, can read
inverted codes, uses Material Design and doesn't do anything else but decoding
a barcode.
<a href="https://play.google.com/store/apps/details?id=de.markusfisch.android.binaryeye"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png" height="45px"/></a>
And it's written in [Kotlin][kotlin] because I've wanted to explore that a
bit more.
+5 -2
View File
@@ -14,8 +14,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 1
versionName '1.0.0'
versionCode 8
versionName '1.3.2'
renderscriptTargetApi 24
renderscriptSupportModeEnabled true
@@ -27,6 +27,9 @@ android {
}
release {
proguardFiles \
getDefaultProguardFile('proguard-android.txt'), \
'proguard-rules.pro'
minifyEnabled true
shrinkResources true
}
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
-dontwarn com.google.**
-dontwarn android.support.v8.**
-keep class android.support.v8.renderscript.** { *; }
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+10 -1
View File
@@ -17,6 +17,7 @@
tools:ignore="UnusedAttribute"
android:allowBackup="true"
android:fullBackupContent="true"
android:supportsRtl="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
@@ -30,8 +31,13 @@
</intent-filter>
</activity>
<activity
android:name=".activity.MainActivity"
android:name=".activity.CameraActivity"
android:label="@string/scan_code">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -42,5 +48,8 @@
android:scheme="http"/>
</intent-filter>
</activity>
<activity
android:name=".activity.MainActivity"
android:label="@string/app_name"/>
</application>
</manifest>
@@ -0,0 +1,351 @@
package de.markusfisch.android.binaryeye.activity
import com.google.zxing.Result
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.rs.YuvToGray
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.SharedPreferences
import android.hardware.Camera
import android.net.Uri
import android.os.Bundle
import android.os.Vibrator
import android.preference.PreferenceManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.widget.SeekBar
import android.widget.Toast
class CameraActivity : AppCompatActivity() {
companion object {
private val REQUEST_CAMERA = 1
private val ZOOM_MAX = "zoom_max"
private val ZOOM_LEVEL = "zoom_level"
}
private val zxing = Zxing()
private val decodingRunnable = Runnable {
while (decoding) {
val result = decodeFrame()
if (result != null) {
cameraView.post { found(result) }
decoding = false
break
}
}
}
private lateinit var preferences: SharedPreferences
private lateinit var vibrator: Vibrator
private lateinit var cameraView: CameraView
private lateinit var zoomBar: SeekBar
private lateinit var yuvToGray: YuvToGray
private var decoding = false
private var decodingThread: Thread? = null
private var frameData: ByteArray? = null
private var frameWidth: Int = 0
private var frameHeight: Int = 0
private var frameOrientation: Int = 0
private var odd = false
private var flash = false
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
when (requestCode) {
REQUEST_CAMERA -> if (grantResults.size > 0 &&
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.no_camera_no_fun,
Toast.LENGTH_SHORT).show()
finish()
}
}
}
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_camera)
initSystemBars(this)
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
preferences = PreferenceManager.getDefaultSharedPreferences(this)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
yuvToGray = YuvToGray(this)
cameraView = findViewById(R.id.camera_view) as CameraView
zoomBar = findViewById(R.id.zoom) as SeekBar
initCameraView()
setTapToFocus()
initZoomBar()
restoreZoom()
initFlashFab(findViewById(R.id.flash))
}
override fun onDestroy() {
super.onDestroy()
yuvToGray.destroy()
saveZoom()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleSendText(intent)
}
override fun onResume() {
super.onResume()
System.gc()
if (hasCameraPermission()) {
cameraView.openAsync(CameraView.findCameraId(
Camera.CameraInfo.CAMERA_FACING_BACK))
startDecoding()
}
}
override fun onPause() {
super.onPause()
cancelDecoding()
cameraView.close()
}
override fun onRestoreInstanceState(savedState: Bundle) {
super.onRestoreInstanceState(savedState)
zoomBar.max = savedState.getInt(ZOOM_MAX)
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(ZOOM_MAX, zoomBar.max)
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
super.onSaveInstanceState(outState)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_camera, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.create -> {
startActivity(MainActivity.getEncodeIntent(this))
true
}
R.id.info -> {
openReadme()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun openReadme() {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(
"https://github.com/markusfisch/BinaryEye/blob/master/README.md")))
}
private fun handleSendText(intent: Intent) {
if (!Intent.ACTION_SEND.equals(intent.getAction()) ||
!"text/plain".equals(intent.getType())) {
return
}
var text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text == null || text.isEmpty()) {
return
}
// consume this intent
intent.setAction(null)
startActivity(MainActivity.getEncodeIntent(this, text))
}
private fun hasCameraPermission(): Boolean {
val permission = android.Manifest.permission.CAMERA
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission),
REQUEST_CAMERA)
return false
}
return true
}
private fun initCameraView() {
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
override fun onConfigureParameters(
parameters: Camera.Parameters) {
if (parameters.isZoomSupported()) {
val max = parameters.getMaxZoom()
if (zoomBar.max != max) {
zoomBar.max = max
zoomBar.progress = max / 3
saveZoom()
}
parameters.setZoom(zoomBar.progress)
} else {
zoomBar.visibility = View.GONE
}
for (mode in parameters.getSupportedSceneModes()) {
if (mode.equals(Camera.Parameters.SCENE_MODE_BARCODE)) {
parameters.setSceneMode(mode)
break
}
}
CameraView.setAutoFocus(parameters)
}
override fun onCameraError() {
Toast.makeText(this@CameraActivity, R.string.camera_error,
Toast.LENGTH_SHORT).show()
finish()
}
override fun onCameraStarted(camera: Camera) {
frameWidth = cameraView.frameWidth
frameHeight = cameraView.frameHeight
frameOrientation = cameraView.frameOrientation
camera.setPreviewCallback { data, _ -> frameData = data }
}
override fun onCameraStopping(camera: Camera) {
camera.setPreviewCallback(null)
}
})
}
private fun setTapToFocus() {
val runnable = Runnable {
cameraView.setFocusArea(null)
}
cameraView.setOnTouchListener({ v: View, event: MotionEvent ->
val camera = cameraView.getCamera()
camera?.cancelAutoFocus()
cameraView.setFocusArea(cameraView.calculateFocusRect(
event.getX(), event.getY(), 100))
camera?.autoFocus({ _: Boolean, _: Camera ->
cameraView.removeCallbacks(runnable)
cameraView.postDelayed(runnable, 3000)
})
v.performClick()
true
})
}
private fun initZoomBar() {
zoomBar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int,
fromUser: Boolean) {
setZoom(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
private fun setZoom(zoom: Int) {
val camera: Camera? = cameraView.getCamera()
camera?.let {
try {
val params = camera.getParameters()
params.setZoom(zoom)
camera.setParameters(params)
} catch (e: RuntimeException) {
// ignore; there's nothing we can do
}
}
}
private fun saveZoom() {
val editor = preferences.edit()
editor.putInt(ZOOM_MAX, zoomBar.max)
editor.putInt(ZOOM_LEVEL, zoomBar.progress)
editor.apply()
}
private fun restoreZoom() {
zoomBar.max = preferences.getInt(ZOOM_MAX, zoomBar.max)
zoomBar.progress = preferences.getInt(ZOOM_LEVEL, zoomBar.progress)
}
private fun initFlashFab(fab: View) {
if (!packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_FLASH)) {
fab.visibility = View.GONE
} else {
fab.setOnClickListener { _ ->
toggleTorchMode()
}
}
}
private fun toggleTorchMode() {
val camera = cameraView.getCamera()
val parameters = camera?.getParameters()
parameters?.setFlashMode(if (flash)
Camera.Parameters.FLASH_MODE_OFF
else
Camera.Parameters.FLASH_MODE_TORCH)
flash = flash xor true
parameters?.let { camera.setParameters(parameters) }
}
private fun startDecoding() {
frameData = null
decoding = true
decodingThread = Thread(decodingRunnable)
decodingThread?.start()
}
private fun cancelDecoding() {
decoding = false
if (decodingThread != null) {
var retry = 100
while (retry-- > 0) {
try {
decodingThread?.join()
break
} catch (e: InterruptedException) {
decodingThread?.interrupt()
}
}
}
}
private fun decodeFrame(): Result? {
frameData ?: return null
return zxing.decodeBitmap(yuvToGray.convert(
frameData!!, frameWidth, frameHeight, frameOrientation))
}
private fun found(result: Result) {
cancelDecoding()
vibrator.vibrate(100)
startActivity(MainActivity.getDecodeIntent(this, result.text,
result.getBarcodeFormat()))
}
}
@@ -1,41 +1,38 @@
package de.markusfisch.android.binaryeye.activity
import de.markusfisch.android.cameraview.widget.CameraView
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.fragment.CameraFragment
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
import de.markusfisch.android.binaryeye.widget.LockOnView
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.R
import android.content.pm.PackageManager
import android.os.Build
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.app.FragmentManager
import android.support.v4.content.ContextCompat
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.app.ActionBar
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.Window
import android.widget.Toast
class MainActivity : AppCompatActivity() {
lateinit var cameraView: CameraView
lateinit var lockOnView: LockOnView
companion object {
private val ENCODE = "encode"
private val DECODE = "decode"
private val DECODE_FORMAT = "decode_format"
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
when (requestCode) {
REQUEST_CAMERA -> if (grantResults.size > 0 &&
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,
R.string.no_camera_no_fun,
Toast.LENGTH_SHORT).show()
finish()
}
fun getEncodeIntent(context: Context, text: String? = ""): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(ENCODE, text)
return intent
}
fun getDecodeIntent(context: Context, text: String,
format: BarcodeFormat): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(DECODE, text)
intent.putExtra(DECODE_FORMAT, format)
return intent
}
}
@@ -54,71 +51,23 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
initSystemBars(this)
cameraView = findViewById(R.id.camera_view) as CameraView
lockOnView = findViewById(R.id.lock_on) as LockOnView
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
setUpListener()
checkPermissions()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (state == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.content_frame, CameraFragment())
.commit()
}
}
private fun checkPermissions() {
val permission = android.Manifest.permission.CAMERA
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission),
REQUEST_CAMERA)
}
}
private fun setUpListener() {
supportFragmentManager.addOnBackStackChangedListener { canBack() }
canBack()
}
private fun canBack() {
supportActionBar?.setDisplayHomeAsUpEnabled(
supportFragmentManager.backStackEntryCount > 0)
}
companion object {
private val REQUEST_CAMERA = 1
private fun initSystemBars(activity: AppCompatActivity?) {
val view = activity?.findViewById(R.id.main_layout)
if (view != null && setSystemBarColor(activity.window,
ContextCompat.getColor(activity,
R.color.primary_dark_translucent))) {
view.setPadding(
0,
SystemBarMetrics.getStatusBarHeight(
activity.resources),
0,
0)
var fragment: Fragment
if (intent?.hasExtra(ENCODE) == true) {
fragment = EncodeFragment.newInstance(
intent.getStringExtra(ENCODE))
} else if (intent?.hasExtra(DECODE) == true) {
fragment = DecodeFragment.newInstance(
intent.getStringExtra(DECODE),
intent.getSerializableExtra(
DECODE_FORMAT) as BarcodeFormat)
} else {
fragment = DecodeFragment()
}
}
private fun setSystemBarColor(window: Window, color: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false
}
window.statusBarColor = color
window.navigationBarColor = color
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
return true
setFragment(supportFragmentManager, fragment)
}
}
}
@@ -12,7 +12,7 @@ class SplashActivity : AppCompatActivity() {
// because that would happen after the app is fully
// initialized what is too late
startActivity(Intent(applicationContext, MainActivity::class.java))
startActivity(Intent(applicationContext, CameraActivity::class.java))
finish()
}
}
@@ -0,0 +1,21 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.R
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
fun setFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).commit() }
}
fun addFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
}
private fun getTransaction(
fm: FragmentManager,
fragment: Fragment): FragmentTransaction {
return fm.beginTransaction().replace(R.id.content_frame, fragment)
}
@@ -0,0 +1,39 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
import de.markusfisch.android.binaryeye.R
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.view.Window
fun initSystemBars(activity: AppCompatActivity?) {
val view = activity?.findViewById(R.id.main_layout)
if (view != null && setSystemBarColor(activity.window,
ContextCompat.getColor(activity,
R.color.primary_dark_translucent))) {
view.setPadding(
0,
SystemBarMetrics.getStatusBarHeight(
activity.resources),
0,
0)
}
}
private fun setSystemBarColor(window: Window, color: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false
}
window.statusBarColor = color
window.navigationBarColor = color
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
return true
}
@@ -0,0 +1,69 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
import android.os.Bundle
import android.graphics.Bitmap
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
class BarcodeFragment : Fragment() {
companion object {
private val CONTENT = "content"
private val FORMAT = "format"
private val SIZE = "size"
fun newInstance(
content: String,
format: BarcodeFormat,
size: Int): Fragment {
val args = Bundle()
args.putString(CONTENT, content)
args.putSerializable(FORMAT, format)
args.putInt(SIZE, size)
val fragment = BarcodeFragment()
fragment.setArguments(args)
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?): View? {
activity.setTitle(R.string.view_barcode)
val view = inflater.inflate(
R.layout.fragment_barcode,
container,
false)
val args = getArguments()
args?.let {
val size = args.getInt(SIZE)
val bitmap: Bitmap?
try {
bitmap = Zxing.encodeAsBitmap(
args.getString(CONTENT),
args.getSerializable(FORMAT) as BarcodeFormat,
size,
size)
} catch (e: Exception) {
Toast.makeText(activity, e.message,
Toast.LENGTH_SHORT).show()
fragmentManager.popBackStack()
return null
}
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
}
return view
}
}
@@ -1,224 +0,0 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.Result
import com.google.zxing.ResultPoint
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.binaryeye.activity.MainActivity
import de.markusfisch.android.binaryeye.rs.Inverter
import de.markusfisch.android.binaryeye.rs.Rotator
import de.markusfisch.android.binaryeye.rs.YuvToBitmap
import de.markusfisch.android.binaryeye.widget.LockOnView
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Rect
import android.hardware.Camera
import android.os.Bundle
import android.os.Vibrator
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class CameraFragment : Fragment() {
private val zxing = Zxing()
private val decodingRunnable = Runnable {
while (decoding) {
val result = decodeFrame()
if (result != null) {
cameraView.post { found(result) }
decoding = false
break
}
}
}
private lateinit var vibrator: Vibrator
private lateinit var cameraView: CameraView
private lateinit var lockOnView: LockOnView
private lateinit var yuvToBitmap: YuvToBitmap
private lateinit var rotator: Rotator
private lateinit var inverter: Inverter
private var decoding = false
private var decodingThread: Thread? = null
private var frameData: ByteArray? = null
private var frameWidth: Int = 0
private var frameHeight: Int = 0
private var frameOrientation: Int = 0
private var odd = false
private var flash = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?): View? {
activity.setTitle(R.string.scan_code)
vibrator = activity.getSystemService(
Context.VIBRATOR_SERVICE) as Vibrator
yuvToBitmap = YuvToBitmap(context)
rotator = Rotator(context)
inverter = Inverter(context)
cameraView = (activity as MainActivity).cameraView
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
override fun onConfigureParameters(parameters: Camera.Parameters) {}
override fun onCameraStarted(camera: Camera) {
frameWidth = cameraView.frameWidth
frameHeight = cameraView.frameHeight
frameOrientation = cameraView.frameOrientation
camera.setPreviewCallback { data, _ -> frameData = data }
}
override fun onCameraStopping(camera: Camera) {
camera.setPreviewCallback(null)
}
})
lockOnView = (activity as MainActivity).lockOnView
val view = inflater.inflate(
R.layout.fragment_camera,
container,
false)
val fab = view.findViewById<View>(R.id.flash)
if (context.packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_FLASH)) {
fab.setOnClickListener { _ -> toggleTorchMode() }
} else {
fab.visibility = View.GONE
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
yuvToBitmap.destroy()
rotator.destroy()
inverter.destroy()
}
override fun onResume() {
super.onResume()
System.gc()
lockOnView.visibility = View.VISIBLE
cameraView.visibility = View.VISIBLE
cameraView.openAsync(CameraView.findCameraId(
Camera.CameraInfo.CAMERA_FACING_BACK))
startDecoding()
}
override fun onPause() {
super.onPause()
cancelDecoding()
cameraView.close()
cameraView.visibility = View.GONE
lockOnView.visibility = View.GONE
}
private fun toggleTorchMode() {
val camera = cameraView.getCamera()
val parameters = camera?.getParameters()
parameters?.setFlashMode(if (flash)
Camera.Parameters.FLASH_MODE_OFF
else
Camera.Parameters.FLASH_MODE_TORCH);
flash = flash xor true
parameters?.let { camera.setParameters(parameters) }
}
private fun startDecoding() {
activity ?: return
frameData = null
decoding = true
decodingThread = Thread(decodingRunnable)
decodingThread?.start()
}
private fun cancelDecoding() {
decoding = false
if (decodingThread != null) {
var retry = 100
while (retry-- > 0) {
try {
decodingThread?.join()
break
} catch (e: InterruptedException) {
// try again
}
}
}
}
private fun decodeFrame(): Result? {
frameData ?: return null
var bitmap = yuvToBitmap.convert(frameData!!, frameWidth, frameHeight)
bitmap = rotator.convert(bitmap, frameOrientation)
odd = odd xor true
if (odd) {
bitmap = inverter.convert(bitmap)
}
return zxing.decodeBitmap(bitmap)
}
private fun found(result: Result) {
cancelDecoding()
vibrator.vibrate(100)
lockOnView.lock(getRelativeBounds(result.resultPoints), 250)
lockOnView.postDelayed({
fragmentManager?.beginTransaction()
?.replace(R.id.content_frame,
ResultFragment.newInstance(result.text))
?.addToBackStack(null)
?.commit()
}, 500)
}
private fun getRelativeBounds(resultPoints: Array<ResultPoint>): Rect {
val fw: Float
val fh: Float
if (frameOrientation == 90 || frameOrientation == 270) {
fw = frameHeight.toFloat()
fh = frameWidth.toFloat()
} else {
fw = frameWidth.toFloat()
fh = frameHeight.toFloat()
}
val viewRect = cameraView.previewRect
val xf = fw / viewRect.width()
val yf = fh / viewRect.height()
val bounds = getBounds(resultPoints)
bounds.set(
Math.round(bounds.left / xf),
Math.round(bounds.top / yf),
Math.round(bounds.right / xf),
Math.round(bounds.bottom / yf))
bounds.offset(viewRect.left, viewRect.top)
return bounds
}
private fun getBounds(resultPoints: Array<ResultPoint>): Rect {
val bounds = Rect(0xffffff, 0xffffff, 0, 0)
for (resultPoint in resultPoints) {
val x = Math.round(resultPoint.x)
val y = Math.round(resultPoint.y)
bounds.set(
Math.min(bounds.left, x),
Math.min(bounds.top, y),
Math.max(bounds.right, x),
Math.max(bounds.bottom, y))
}
return bounds
}
}
@@ -1,8 +1,10 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.R
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -15,23 +17,26 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.EditText
import android.widget.Toast
class ResultFragment : Fragment() {
class DecodeFragment : Fragment() {
companion object {
private val RESULT = "result"
private val CONTENT = "content"
private val FORMAT = "format"
fun newInstance(content: String): Fragment {
fun newInstance(content: String, format: BarcodeFormat): Fragment {
val args = Bundle()
args.putString(RESULT, content)
val fragment = ResultFragment()
args.putString(CONTENT, content)
args.putSerializable(FORMAT, format)
val fragment = DecodeFragment()
fragment.arguments = args
return fragment
}
}
private lateinit var content: String
private lateinit var contentView: EditText
private lateinit var format: BarcodeFormat
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@@ -42,43 +47,54 @@ class ResultFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?): View {
activity.setTitle(R.string.result)
content = arguments?.getString(RESULT) ?: ""
activity.setTitle(R.string.content)
val view = inflater.inflate(
R.layout.fragment_result,
R.layout.fragment_decode,
container,
false)
val textView = view.findViewById<TextView>(R.id.text)
textView.text = content
val content = arguments?.getString(CONTENT) ?: ""
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?:
BarcodeFormat.QR_CODE
contentView = view.findViewById<EditText>(R.id.content)
contentView.setText(content)
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
share(content)
share(getContent())
}
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_result, menu)
inflater.inflate(R.menu.fragment_decode, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.copy_to_clipboard -> {
copyToClipboard(content)
copyToClipboard(getContent())
true
}
R.id.open_url -> {
openUrl(content)
openUrl(getContent())
true
}
R.id.create -> {
addFragment(fragmentManager,
EncodeFragment.newInstance(getContent(), format))
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun getContent(): String {
return contentView.getText().toString()
}
private fun copyToClipboard(text: String) {
activity ?: return
@@ -0,0 +1,136 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.inputmethod.InputMethodManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.SeekBar
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
class EncodeFragment : Fragment() {
companion object {
private val CONTENT = "content"
private val FORMAT = "format"
fun newInstance(
content: String,
format: BarcodeFormat = BarcodeFormat.AZTEC): Fragment {
val args = Bundle()
args.putString(CONTENT, content)
args.putSerializable(FORMAT, format)
val fragment = EncodeFragment()
fragment.setArguments(args)
return fragment
}
}
private lateinit var formatView: Spinner
private lateinit var sizeView: TextView
private lateinit var sizeBarView: SeekBar
private val writers = arrayListOf(
BarcodeFormat.AZTEC,
BarcodeFormat.CODABAR,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_128,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.ITF,
BarcodeFormat.PDF_417,
BarcodeFormat.QR_CODE,
BarcodeFormat.UPC_A
)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?): View {
activity.setTitle(R.string.compose_barcode)
val view = inflater.inflate(
R.layout.fragment_encode,
container,
false)
formatView = view.findViewById<Spinner>(R.id.format)
formatView.setAdapter(ArrayAdapter<String>(
activity,
android.R.layout.simple_list_item_1,
writers.map { it -> it.name }))
sizeView = view.findViewById<TextView>(R.id.size_display)
sizeBarView = view.findViewById<SeekBar>(R.id.size_bar)
initSizeBar()
val contentView = view.findViewById<EditText>(R.id.content)
val args = getArguments()
args?.let {
contentView.setText(args.getString(CONTENT))
formatView.setSelection(writers.indexOf(
args.getSerializable(FORMAT) as BarcodeFormat?))
}
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
val format = writers.get(formatView.getSelectedItemPosition())
var size = getSize(sizeBarView.getProgress())
val content = contentView.getText().toString()
if (content.isEmpty()) {
Toast.makeText(v.context, R.string.error_no_content,
Toast.LENGTH_SHORT).show()
} else {
hideSoftKeyboard(contentView)
addFragment(fragmentManager, BarcodeFragment.newInstance(
content,
format,
size))
}
}
return view
}
private fun initSizeBar() {
updateSize(sizeBarView.getProgress())
sizeBarView.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar,
progressValue: Int,
fromUser: Boolean) {
updateSize(progressValue)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
private fun updateSize(power: Int) {
val size = getSize(power)
sizeView.setText("${size}x${size}")
}
private fun getSize(power: Int) = 128 * (power + 1)
private fun hideSoftKeyboard(view: View) {
val im = activity?.getSystemService(
Context.INPUT_METHOD_SERVICE) as InputMethodManager?
im?.let {
im.hideSoftInputFromWindow(view.getWindowToken(), 0)
}
}
}
@@ -1,31 +0,0 @@
package de.markusfisch.android.binaryeye.rs
import de.markusfisch.android.binaryeye.renderscript.ScriptC_invert
import android.content.Context
import android.graphics.Bitmap
import android.support.v8.renderscript.Allocation
import android.support.v8.renderscript.RenderScript
class Inverter(context: Context) {
private val rs: RenderScript = RenderScript.create(context)
private val inverterScript: ScriptC_invert = ScriptC_invert(rs)
fun destroy() {
inverterScript.destroy()
rs.destroy()
}
fun convert(bitmap: Bitmap): Bitmap {
val allocation = Allocation.createFromBitmap(
rs,
bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT)
inverterScript.forEach_invert(allocation, allocation)
allocation.copyTo(bitmap)
return bitmap
}
}
@@ -1,71 +0,0 @@
package de.markusfisch.android.binaryeye.rs
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import android.content.Context
import android.graphics.Bitmap
import android.support.v8.renderscript.Allocation
import android.support.v8.renderscript.RenderScript
class Rotator(context: Context) {
private val rs: RenderScript = RenderScript.create(context)
private var rotatorScript: ScriptC_rotator = ScriptC_rotator(rs)
fun destroy() {
rotatorScript.destroy()
rs.destroy()
}
fun convert(bitmap: Bitmap, frameOrientation: Int): Bitmap {
var orientation = frameOrientation
var width = bitmap.width
var height = bitmap.height
orientation = orientation % 360 / 90
if (orientation == 0) {
return bitmap
}
rotatorScript._inWidth = width
rotatorScript._inHeight = height
val sourceAllocation = Allocation.createFromBitmap(
rs,
bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT)
rotatorScript._inImage = sourceAllocation
when (orientation) {
1, 3 -> {
val tmp = width
width = height
height = tmp
}
}
val target = Bitmap.createBitmap(
width,
height,
Bitmap.Config.ARGB_8888)
val targetAllocation = Allocation.createFromBitmap(
rs,
target,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT)
when (orientation) {
1 -> rotatorScript.forEach_rotate90(
targetAllocation,
targetAllocation)
2 -> rotatorScript.forEach_rotate180(
targetAllocation,
targetAllocation)
3 -> rotatorScript.forEach_rotate270(
targetAllocation,
targetAllocation)
}
targetAllocation.copyTo(target)
return target
}
}
@@ -1,64 +0,0 @@
package de.markusfisch.android.binaryeye.rs
import android.content.Context
import android.graphics.Bitmap
import android.support.v8.renderscript.Allocation
import android.support.v8.renderscript.Element
import android.support.v8.renderscript.RenderScript
import android.support.v8.renderscript.ScriptIntrinsicYuvToRGB
import android.support.v8.renderscript.Type
class YuvToBitmap(context: Context) {
private val rs: RenderScript = RenderScript.create(context)
private var yuvType: Type? = null
private var rgbaType: Type? = null
private var yuvAllocation: Allocation? = null
private var rgbaAllocation: Allocation? = null
private var yuvToRgbaScript: ScriptIntrinsicYuvToRGB? = null
fun destroy() {
yuvToRgbaScript?.destroy()
yuvToRgbaScript = null
yuvType?.destroy()
yuvType = null
rgbaType?.destroy()
rgbaType = null
yuvAllocation?.destroy()
yuvAllocation = null
rgbaAllocation?.destroy()
rgbaAllocation = null
rs.destroy()
}
fun convert(data: ByteArray, width: Int, height: Int): Bitmap {
if (yuvAllocation == null) {
init(data, width, height)
}
yuvAllocation?.copyFrom(data)
yuvToRgbaScript?.forEach(rgbaAllocation)
val bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888)
rgbaAllocation?.copyTo(bitmap)
return bitmap
}
private fun init(data: ByteArray, width: Int, height: Int) {
yuvType = Type.createX(rs, Element.U8(rs), data.size)
yuvType?.let {
yuvAllocation = Allocation.createTyped(rs, yuvType,
Allocation.USAGE_SCRIPT)
}
rgbaType = Type.createXY(rs, Element.RGBA_8888(rs), width, height)
rgbaType?.let {
rgbaAllocation = Allocation.createTyped(rs, rgbaType)
}
yuvToRgbaScript = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
yuvToRgbaScript?.setInput(yuvAllocation)
}
}
@@ -0,0 +1,104 @@
package de.markusfisch.android.binaryeye.rs
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import de.markusfisch.android.binaryeye.renderscript.ScriptC_yuv2gray
import android.content.Context
import android.graphics.Bitmap
import android.support.v8.renderscript.Allocation
import android.support.v8.renderscript.Element
import android.support.v8.renderscript.RenderScript
import android.support.v8.renderscript.Type
class YuvToGray(context: Context) {
private val rs: RenderScript = RenderScript.create(context)
private val rotatorScript: ScriptC_rotator = ScriptC_rotator(rs)
private val yuv2grayScript: ScriptC_yuv2gray = ScriptC_yuv2gray(rs)
private var yuvType: Type? = null
private var rgbaType: Type? = null
private var yuvAlloc: Allocation? = null
private var rgbaAlloc: Allocation? = null
private var destAlloc: Allocation? = null
private var dest: Bitmap? = null
private var odd = true
fun destroy() {
yuvType?.destroy()
yuvType = null
rgbaType?.destroy()
rgbaType = null
yuvAlloc?.destroy()
yuvAlloc = null
rgbaAlloc?.destroy()
rgbaAlloc = null
destAlloc?.destroy()
destAlloc = null
dest?.recycle()
dest = null
rotatorScript.destroy()
yuv2grayScript.destroy()
rs.destroy()
}
fun convert(
data: ByteArray,
width: Int,
height: Int,
orientation: Int): Bitmap {
if (dest == null) {
yuvType = Type.createXY(rs, Element.U8(rs), width, height * 3 / 2)
yuvAlloc = Allocation.createTyped(rs, yuvType,
Allocation.USAGE_SCRIPT)
rgbaType = Type.createXY(rs, Element.RGBA_8888(rs), width, height)
rgbaAlloc = Allocation.createTyped(rs, rgbaType,
Allocation.USAGE_SCRIPT)
var w = width
var h = height
when (orientation) {
90, 270 -> {
val tmp = w
w = h
h = tmp
}
}
dest = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
destAlloc = Allocation.createFromBitmap(
rs,
dest,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT)
}
yuvAlloc?.copyFrom(data)
yuv2grayScript._inYUV = yuvAlloc
// width/height are uint_32 but Kotlin wants toLong()
yuv2grayScript._inWidth = width.toLong()
yuv2grayScript._inHeight = height.toLong()
// invert every second frame to also read inverted barcodes
if (odd) {
yuv2grayScript.forEach_yuv2gray(rgbaAlloc)
} else {
yuv2grayScript.forEach_yuv2inverted(rgbaAlloc)
}
odd = odd xor true
rotatorScript._inImage = rgbaAlloc
rotatorScript._inWidth = width
rotatorScript._inHeight = height
when (orientation) {
0 -> destAlloc?.copyFrom(rgbaAlloc)
90 -> rotatorScript.forEach_rotate90(destAlloc, destAlloc)
180 -> rotatorScript.forEach_rotate180(destAlloc, destAlloc)
270 -> rotatorScript.forEach_rotate270(destAlloc, destAlloc)
}
destAlloc?.copyTo(dest)
return dest!!
}
}
@@ -1,7 +1,10 @@
package de.markusfisch.android.binaryeye.view
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Point
import android.os.Build
import android.util.TypedValue
object SystemBarMetrics {
@@ -26,6 +29,32 @@ object SystemBarMetrics {
0
}
fun getNavigationBarSize(res:Resources):Point {
val size = Point(0, 0)
if (!getIdentifierBoolean(res, "config_showNavigationBar")) {
return size
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
val conf = res.getConfiguration()
if (conf.orientation == Configuration.ORIENTATION_LANDSCAPE &&
// according to https://developer.android.com/training/multiscreen/screensizes.html#TaskUseSWQuali
// only a screen < 600 dp is considered to be a phone
// and can move its navigation bar to the side
conf.smallestScreenWidthDp < 600) {
size.x = getIdentifierDimen(res,
"navigation_bar_height_landscape")
return size
}
}
size.y = getIdentifierDimen(res, "navigation_bar_height")
return size
}
private fun getIdentifierBoolean(res:Resources, name:String):Boolean {
val id = res.getIdentifier(name, "bool", "android")
return id > 0 && res.getBoolean(id)
}
private fun getIdentifierDimen(res: Resources, name: String): Int {
val id = res.getIdentifier(name, "dimen", "android")
return if (id > 0) res.getDimensionPixelSize(id) else 0
@@ -1,260 +0,0 @@
package de.markusfisch.android.binaryeye.widget
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Rect
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import android.view.SurfaceView
import android.view.SurfaceHolder
class LockOnView : SurfaceView {
private val markerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val restRect = Rect()
private val startRect = Rect()
private val endRect = Rect()
private val currentRect = Rect()
private val animationRunnable = Runnable {
setAnimationStart()
while (running) {
val now = System.currentTimeMillis() - animationStart
if (now > animationDuration) {
break
}
animateCurrentRect(now)
lockCanvasAndDraw()
}
lockCanvasAndDraw()
running = false
}
@Volatile private var running = false
private lateinit var markerTopLeft: Bitmap
private lateinit var markerTopRight: Bitmap
private lateinit var markerBottomLeft: Bitmap
private lateinit var markerBottomRight: Bitmap
private var markerSize: Int = 0
private var minSize: Int = 0
private var thread: Thread? = null
private var animationStart: Long = 0
private var animationDuration: Long = 0
init {
loadMarkers(context.resources)
initSurfaceHolder(context)
setZOrderOnTop(true)
}
constructor(context: Context, attrs: AttributeSet) :
super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr) {
}
fun lock(rect: Rect, ms: Int) {
startRect.set(currentRect)
if (running) {
tintMarkers()
fitRect(rect)
endRect.set(rect)
setAnimationStart()
} else {
startAnimation(rect, ms)
}
}
fun reset() {
untintMarkers()
startRect.set(restRect)
currentRect.set(startRect)
lockCanvasAndDraw()
}
private fun loadMarkers(res: Resources) {
val dp = res.displayMetrics.density
markerTopLeft = BitmapFactory.decodeResource(res, R.drawable.marker)
val width = markerTopLeft.width
val height = markerTopLeft.height
markerSize = Math.max(width, height)
minSize = markerSize * 2 + Math.round(dp * 8f)
val matrix = Matrix()
matrix.setScale(-1f, 1f)
markerTopRight = Bitmap.createBitmap(markerTopLeft,
0, 0, width, height, matrix, false);
matrix.setScale(1f, -1f)
markerBottomLeft = Bitmap.createBitmap(markerTopLeft,
0, 0, width, height, matrix, false);
matrix.setScale(-1f, -1f)
markerBottomRight = Bitmap.createBitmap(markerTopLeft,
0, 0, width, height, matrix, false);
}
private fun initSurfaceHolder(context: Context) {
holder.setFormat(PixelFormat.TRANSPARENT)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int) {
initRestRect(context, width, height)
reset()
}
override fun surfaceCreated(holder: SurfaceHolder) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {
cancelAnimation()
}
})
}
private fun cancelAnimation() {
running = false
if (thread != null) {
var retry = 100
while (retry-- > 0) {
try {
thread?.join()
retry = 0
} catch (e: InterruptedException) {
// try again
}
}
}
}
private fun startAnimation(rect: Rect, ms: Int) {
if (running) {
return
}
if (rect == restRect) {
untintMarkers()
} else {
tintMarkers()
}
running = true
fitRect(rect)
endRect.set(rect)
animationDuration = ms.toLong()
thread = Thread(animationRunnable)
thread?.start()
}
private fun initRestRect(context: Context, width: Int, height: Int) {
val res = context.resources
val dp = res.displayMetrics.density
val padding = Math.round(16f * dp)
var left = padding
var top = padding + if (width > height)
SystemBarMetrics.getStatusAndToolBarHeight(context)
else
0
val paddingLeftRight = padding * 2
val paddingTopBottom = top + padding
val innerWidth = width - paddingLeftRight
val innerHeight = height - paddingTopBottom
val size = Math.min(innerWidth, innerHeight)
left += (innerWidth - size) / 2
top += (innerHeight - size) / 2
restRect.set(left, top, left + size, top + size)
}
private fun fitRect(rect: Rect) {
val width = rect.width()
val height = rect.height()
if (width < minSize) {
val pad = (minSize - width) / 2
rect.left -= pad
rect.right += pad
}
if (height < minSize) {
val pad = (minSize - height) / 2
rect.top -= pad
rect.bottom += pad
}
}
private fun setAnimationStart() {
animationStart = System.currentTimeMillis()
}
private fun animateCurrentRect(now: Long) {
currentRect.set(
linear(now,
startRect.left,
endRect.left - startRect.left,
animationDuration),
linear(now,
startRect.top,
endRect.top - startRect.top,
animationDuration),
linear(now,
startRect.right,
endRect.right - startRect.right,
animationDuration),
linear(now,
startRect.bottom,
endRect.bottom - startRect.bottom,
animationDuration))
}
private fun linear(
time: Long,
begin: Int,
change: Int,
duration: Long): Int = Math.round(
change.toFloat() * time / duration + begin)
private fun lockCanvasAndDraw() {
val canvas = holder.lockCanvas()
if (canvas != null) {
drawMarkers(canvas, currentRect)
holder.unlockCanvasAndPost(canvas)
}
}
private fun untintMarkers() {
markerPaint.colorFilter = null
}
private fun tintMarkers() {
markerPaint.colorFilter = PorterDuffColorFilter(
ContextCompat.getColor(context, R.color.accent),
PorterDuff.Mode.SRC_IN)
}
private fun drawMarkers(canvas: Canvas, rect: Rect) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
canvas.drawBitmap(markerTopLeft,
rect.left.toFloat(),
rect.top.toFloat(),
markerPaint)
canvas.drawBitmap(markerTopRight,
(rect.right - markerSize).toFloat(),
rect.top.toFloat(),
markerPaint)
canvas.drawBitmap(markerBottomLeft,
rect.left.toFloat(),
(rect.bottom - markerSize).toFloat(),
markerPaint)
canvas.drawBitmap(markerBottomRight,
(rect.right - markerSize).toFloat(),
(rect.bottom - markerSize).toFloat(),
markerPaint)
}
}
@@ -5,6 +5,7 @@ import com.google.zxing.BinaryBitmap
import com.google.zxing.DecodeHintType
import com.google.zxing.LuminanceSource
import com.google.zxing.MultiFormatReader
import com.google.zxing.MultiFormatWriter
import com.google.zxing.ReaderException
import com.google.zxing.Result
import com.google.zxing.RGBLuminanceSource
@@ -17,6 +18,36 @@ import java.util.EnumMap
import java.util.EnumSet
class Zxing {
companion object {
private val black = 0xff000000.toInt()
private val white = 0xffffffff.toInt()
fun encodeAsBitmap(
text: String,
format: BarcodeFormat,
width: Int,
height: Int): Bitmap? {
val result = MultiFormatWriter().encode(text, format,
width, height, null)
val w = result.getWidth()
val h = result.getHeight()
val pixels = IntArray(w * h)
var offset = 0
for (y in 0..h - 1) {
for (x in 0..w - 1) {
pixels[offset + x] = if (result.get(x, y))
black
else
white
}
offset += w
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
return bitmap
}
}
private val multiFormatReader: MultiFormatReader = MultiFormatReader()
init {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line"
android:gravity="center">
<stroke
android:width="1dp"
android:color="@color/accent"/>
</shape>
@@ -0,0 +1,8 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/background_color"/>
<item>
<bitmap
android:gravity="top|right"
android:src="@drawable/wallpaper"/>
</item>
</layer-list>
@@ -0,0 +1,16 @@
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/accent"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/accent_dark"/>
</shape>
</item>
</selector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>
@@ -0,0 +1,39 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<de.markusfisch.android.cameraview.widget.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:background="@drawable/background_scanner">
<include layout="@layout/toolbar"/>
<SeekBar
android:id="@+id/zoom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="8dp"
android:layout_marginRight="80dp"
android:layout_marginEnd="80dp"
android:padding="16dp"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:src="@drawable/ic_action_flash"
app:elevation="4dp"/>
</RelativeLayout>
</merge>
-10
View File
@@ -1,14 +1,4 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<de.markusfisch.android.cameraview.widget.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<de.markusfisch.android.binaryeye.widget.LockOnView
android:id="@+id/lock_on"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
@@ -0,0 +1,6 @@
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/barcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
@@ -1,18 +0,0 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:src="@drawable/ic_action_flash"
app:elevation="4dp"/>
</RelativeLayout>
@@ -4,11 +4,15 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
<EditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"/>
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="start|top"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:typeface="monospace"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/share"
@@ -0,0 +1,61 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/format"/>
<Spinner
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/size"/>
<TextView
android:id="@+id/size_display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"/>
<SeekBar
android:id="@+id/size_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:progress="4"
android:max="7"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/content"/>
<EditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:hint="@string/input_content_here"/>
<Button
style="@style/Button"
android:id="@+id/encode"
android:layout_width="match_parent"
android:text="@string/encode"/>
</LinearLayout>
</ScrollView>
+14
View File
@@ -0,0 +1,14 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/info"
android:title="@string/info"
android:icon="@drawable/ic_action_info"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/create"
android:title="@string/compose_barcode"
android:icon="@drawable/ic_action_create"
material:showAsAction="ifRoom"/>
</menu>
@@ -11,4 +11,9 @@
android:title="@string/open_url"
android:icon="@drawable/ic_action_open"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/create"
android:title="@string/compose_barcode"
android:icon="@drawable/ic_action_create"
material:showAsAction="ifRoom"/>
</menu>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

+14
View File
@@ -0,0 +1,14 @@
<resources>
<style
name="Button"
parent="@android:style/Widget.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/button_raised</item>
<item name="android:textColor">@color/button_text</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingLeft">@dimen/button_padding</item>
<item name="android:paddingRight">@dimen/button_padding</item>
<item name="android:maxLines">1</item>
</style>
</resources>
+4 -1
View File
@@ -3,5 +3,8 @@
<color name="primary_translucent">#8222</color>
<color name="primary_dark">#111</color>
<color name="primary_dark_translucent">#8111</color>
<color name="accent">#26e1cf</color>
<color name="accent">#b6d46f</color>
<color name="accent_dark">#a6c45f</color>
<color name="background_color">#111</color>
<color name="button_text">#fff</color>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<dimen name="button_padding">8dp</dimen>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Binary Eye</string>
</resources>
+11 -5
View File
@@ -1,12 +1,18 @@
<resources>
<string name="app_name">Binary Eye</string>
<string name="no_camera_no_fun">This app does not make any sense without camera access. Good bye.</string>
<string name="scan_code">Scan a code</string>
<string name="flash">Toggle flash</string>
<string name="result">Content</string>
<string name="camera_error">Can\'t access camera, please try again</string>
<string name="scan_code">Scan code</string>
<string name="compose_barcode">Compose barcode</string>
<string name="content">Content</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="put_into_clipboard">Put into clipboard</string>
<string name="open_url">Open url</string>
<string name="share">Share</string>
<string name="cannot_resolve_action">No application can open that</string>
<string name="format">Format</string>
<string name="size">Size in pixels</string>
<string name="input_content_here">Input content here</string>
<string name="encode">ENCODE</string>
<string name="error_no_content">Missing content</string>
<string name="view_barcode">View barcode</string>
<string name="info">Info</string>
</resources>
+14
View File
@@ -3,8 +3,22 @@
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@drawable/background_window</item>
</style>
<style name="SplashTheme" parent="@style/AppTheme">
<item name="android:windowBackground">@drawable/background_splash</item>
</style>
<style
name="Button"
parent="@android:style/Widget.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">36dp</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:background">@drawable/button_raised</item>
<item name="android:textColor">@color/button_text</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingLeft">@dimen/button_padding</item>
<item name="android:paddingRight">@dimen/button_padding</item>
<item name="android:maxLines">1</item>
</style>
</resources>
-10
View File
@@ -1,10 +0,0 @@
#pragma version(1)
#pragma rs java_package_name(de.markusfisch.android.binaryeye.renderscript)
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
uchar4 out = in;
out.r = 255 - in.r;
out.g = 255 - in.g;
out.b = 255 - in.b;
return out;
}
+27
View File
@@ -0,0 +1,27 @@
#pragma version(1)
#pragma rs java_package_name(de.markusfisch.android.binaryeye.renderscript)
#pragma rs_fp_relaxed
rs_allocation inYUV;
uint32_t inWidth;
uint32_t inHeight;
uchar4 RS_KERNEL yuv2gray(uint32_t x, uint32_t y) {
uchar Y = rsGetElementAt_uchar(inYUV, x, y);
uchar4 out;
out.r = Y;
out.g = Y;
out.b = Y;
out.a = 255;
return out;
}
uchar4 RS_KERNEL yuv2inverted(uint32_t x, uint32_t y) {
uchar Y = 255 - rsGetElementAt_uchar(inYUV, x, y);
uchar4 out;
out.r = Y;
out.g = Y;
out.b = Y;
out.a = 255;
return out;
}
+11 -5
View File
@@ -1,12 +1,13 @@
buildscript {
ext.kotlin_version = '1.1.4-2'
ext.tools_version = '2.3.3'
ext.build_tools_version = '26.0.0'
ext.sdk_version = 26
ext.kotlin_version = '1.1.51'
ext.tools_version = '3.0.0'
ext.build_tools_version = '26.0.2'
ext.sdk_version = 27
ext.support_version = '25.3.1'
repositories {
jcenter()
google()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
@@ -15,14 +16,19 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:$tools_version"
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.8'
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id 'org.sonarqube' version '2.6'
}
allprojects {
repositories {
jcenter()
google()
}
gradle.projectsEvaluated {
+3 -2
View File
@@ -1,6 +1,7 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
@@ -18,8 +19,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 2
versionName '1.0.1'
versionCode 5
versionName '1.3.0'
}
}
@@ -15,17 +15,20 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class CameraView extends FrameLayout {
public interface OnCameraListener {
void onConfigureParameters(Camera.Parameters parameters);
void onCameraError();
void onCameraStarted(Camera camera);
void onCameraStopping(Camera camera);
}
public final Rect previewRect = new Rect();
private boolean isOpen = false;
private Camera camera;
private int viewWidth;
private int viewHeight;
@@ -56,18 +59,45 @@ public class CameraView extends FrameLayout {
.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRotation()) {
default:
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
case Surface.ROTATION_0:
default:
return 0;
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean setAutoFocus(Camera.Parameters parameters) {
// best for taking pictures, API >= ICE_CREAM_SANDWICH
String continuousPicture =
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
// less aggressive than CONTINUOUS_PICTURE, API >= GINGERBREAD
String continuousVideo =
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
// last resort
String autoFocus = Camera.Parameters.FOCUS_MODE_AUTO;
// prefer feature detection instead of checking BUILD.VERSION
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(continuousPicture)) {
parameters.setFocusMode(continuousPicture);
} else if (focusModes.contains(continuousVideo)) {
parameters.setFocusMode(continuousVideo);
} else if (focusModes.contains(autoFocus)) {
parameters.setFocusMode(autoFocus);
} else {
return false;
}
return true;
}
public CameraView(Context context) {
super(context);
}
@@ -84,15 +114,16 @@ public class CameraView extends FrameLayout {
}
public void openAsync(final int cameraId) {
isOpen = true;
new AsyncTask<Void, Void, Camera>() {
@Override
protected Camera doInBackground(Void... nothings) {
if (camera != null) {
if (CameraView.this.camera != null) {
return null;
}
try {
// open() may take a while so it shouldn't be
// run on the main thread according to the docs
// invoked on the main thread according to the docs
return Camera.open(cameraId);
} catch (RuntimeException e) {
return null;
@@ -101,7 +132,24 @@ public class CameraView extends FrameLayout {
@Override
protected void onPostExecute(Camera camera) {
if (!isOpen) {
// close() was called while Camera.open() was
// running on another thread
if (camera != null) {
// close the open camera that noone is
// interested in anymore
CameraView.this.camera = camera;
close();
}
return;
}
if (camera == null) {
if (onCameraListener != null &&
// run onCameraError() only if there
// isn't an open camera yet
CameraView.this.camera == null) {
onCameraListener.onCameraError();
}
return;
}
CameraView.this.camera = camera;
@@ -119,6 +167,7 @@ public class CameraView extends FrameLayout {
}
public void close() {
isOpen = false;
if (camera != null) {
if (onCameraListener != null) {
onCameraListener.onCameraStopping(camera);
@@ -151,6 +200,43 @@ public class CameraView extends FrameLayout {
return frameOrientation;
}
public Rect calculateFocusRect(float x, float y, int radius) {
int cx = Math.round(2000f / viewWidth * x - 1000f);
int cy = Math.round(2000f / viewHeight * y - 1000f);
return new Rect(
Math.max(-1000, cx - radius),
Math.max(-1000, cy - radius),
Math.min(1000, cx + radius),
Math.min(1000, cy + radius));
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public boolean setFocusArea(Rect area) {
if (camera == null || Build.VERSION.SDK_INT <
Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return false;
}
try {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getMaxNumFocusAreas() > 0) {
if (area != null) {
List<Camera.Area> focusAreas =
new ArrayList<Camera.Area>();
focusAreas.add(new Camera.Area(area, 1000));
parameters.setFocusAreas(focusAreas);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
} else {
parameters.setFocusAreas(null);
CameraView.setAutoFocus(parameters);
}
}
camera.setParameters(parameters);
return true;
} catch (RuntimeException e) {
return false;
}
}
@Override
protected void onLayout(
boolean changed,
@@ -175,14 +261,21 @@ public class CameraView extends FrameLayout {
private void setCameraParameters(Context context) {
boolean transpose = frameOrientation == 90 || frameOrientation == 270;
Camera.Parameters parameters = camera.getParameters();
parameters.setRotation(frameOrientation);
setPreviewSize(parameters, transpose);
setAutoFocus(parameters);
if (onCameraListener != null) {
onCameraListener.onConfigureParameters(parameters);
try {
Camera.Parameters parameters = camera.getParameters();
parameters.setRotation(frameOrientation);
setPreviewSize(parameters, transpose);
if (onCameraListener != null) {
onCameraListener.onConfigureParameters(parameters);
}
camera.setParameters(parameters);
} catch (RuntimeException e) {
if (onCameraListener != null) {
onCameraListener.onCameraError();
}
return;
}
camera.setParameters(parameters);
camera.setDisplayOrientation(frameOrientation);
int childWidth;
@@ -216,9 +309,11 @@ public class CameraView extends FrameLayout {
parameters.getSupportedPreviewSizes(),
frameWidth,
frameHeight);
frameWidth = size.width;
frameHeight = size.height;
parameters.setPreviewSize(frameWidth, frameHeight);
if (size != null) {
frameWidth = size.width;
frameHeight = size.height;
parameters.setPreviewSize(frameWidth, frameHeight);
}
}
private static Camera.Size findBestPreviewSize(
@@ -233,7 +328,7 @@ public class CameraView extends FrameLayout {
Camera.Size bestSizeAspect = null;
for (Camera.Size size : sizes) {
double diff = Math.abs(size.height - height) +
double diff = (double) Math.abs(size.height - height) +
Math.abs(size.width - width);
if (diff < minDiff) {
@@ -253,29 +348,6 @@ public class CameraView extends FrameLayout {
return bestSizeAspect != null ? bestSizeAspect : bestSize;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private static void setAutoFocus(Camera.Parameters parameters) {
// best for taking pictures, API >= ICE_CREAM_SANDWICH
String continuousPicture =
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
// less aggressive than CONTINUOUS_PICTURE, API >= GINGERBREAD
String continuousVideo =
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
// last resort
String autoFocus = Camera.Parameters.FOCUS_MODE_AUTO;
// prefer feature detection instead of checking BUILD.VERSION
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(continuousPicture)) {
parameters.setFocusMode(continuousPicture);
} else if (focusModes.contains(continuousVideo)) {
parameters.setFocusMode(continuousVideo);
} else if (focusModes.contains(autoFocus)) {
parameters.setFocusMode(autoFocus);
}
}
private void addSurfaceView(
Context context,
int surfaceWidth,
@@ -289,6 +361,7 @@ public class CameraView extends FrameLayout {
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// wait until the surface has dimensions
}
@Override
@@ -297,6 +370,9 @@ public class CameraView extends FrameLayout {
int format,
int width,
int height) {
if (camera == null) {
return;
}
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
+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-3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+135
View File
@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="192"
height="192"
viewBox="0 0 192 192"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_launcher.svg"
inkscape:export-filename="/Users/markus/app_icon.png"
inkscape:export-xdpi="240"
inkscape:export-ydpi="240">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.74025241"
inkscape:cx="96"
inkscape:cy="96"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1680"
inkscape:window-height="1005"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1"
showguides="false" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-860.36218)">
<circle
r="88"
cy="958.36218"
cx="96"
id="circle5439"
style="fill:#a6c45f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
style="fill:#b6d46f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path5437"
cx="96"
cy="956.36218"
r="88" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.1254902;fill-rule:nonzero;stroke:none;stroke-width:8.60725021;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 98.019531 32.458984 C 81.273736 32.458984 64.528308 38.840812 51.761719 51.607422 C 26.228537 77.140592 26.228537 118.59177 51.761719 144.125 L 91.40625 183.76953 A 88 88 0 0 0 96 184 A 88 88 0 0 0 184 96 A 88 88 0 0 0 183.82812 91.158203 L 144.27734 51.607422 C 131.51074 38.840812 114.76533 32.459024 98.019531 32.458984 z "
transform="translate(0,860.36218)"
id="path5462" />
<circle
r="64.837967"
cy="957.65448"
cx="97.284081"
id="circle5467"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle5451"
cx="95.43679"
cy="959.80591"
r="35.467094" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle5455"
cx="119.39222"
cy="933.90826"
r="23.307953" />
<flowRoot
xml:space="preserve"
id="flowRoot3776"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="translate(296.8833,136.47977)"><flowRegion
id="flowRegion3778"><rect
id="rect3780"
width="29"
height="71"
x="52"
y="60" /></flowRegion><flowPara
id="flowPara3782" /></flowRoot> <text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
x="4973.2891"
y="3504.5168"
id="text5084"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan5086"
x="4973.2891"
y="3504.5168" /></text>
<g
id="g4345"
transform="matrix(0.20012454,-0.30191033,0.30191033,0.20012454,-179.09304,776.50728)">
<path
transform="matrix(-0.71624605,0,0,0.71624605,252.9668,272.6866)"
id="path4155"
d="m 70.800257,803.10815 c 18.72634,-10.81207 42.064013,-4.55865 52.875453,14.16738 6.21553,10.76497 6.67525,22.98383 2.43735,33.71348 9.95484,0.22482 20.00712,3.82743 29.87632,9.35284 5.70581,-16.36426 17.92669,-30.34 32.41043,-37.86744 a 7.6098878,7.6098878 0 0 1 2.05397,-0.12843 7.6098878,7.6098878 0 0 1 2.05385,-0.12842 7.6098878,7.6098878 0 0 1 3.78076,1.01708 7.6098878,7.6098878 0 0 1 2.18403,1.941 7.6098878,7.6098878 0 0 1 1.16879,5.70957 7.6098878,7.6098878 0 0 1 -0.34046,1.25368 7.6098878,7.6098878 0 0 1 -0.34045,1.25368 7.6098878,7.6098878 0 0 1 -1.13869,1.71405 7.6098878,7.6098878 0 0 1 -2.39442,1.38323 c -12.33033,6.40652 -22.54596,18.62752 -26.1492,32.12255 6.50822,5.40015 12.37934,12.05934 17.69138,19.58251 2.94622,-3.19898 6.27645,-6.41509 9.78804,-8.85073 10.09338,-6.95039 22.03067,-10.77888 33.55578,-11.9223 a 7.6098878,7.6098878 0 0 1 1.25571,0.33189 7.6098878,7.6098878 0 0 1 2.51017,0.66378 7.6098878,7.6098878 0 0 1 2.18447,1.94102 7.6098878,7.6098878 0 0 1 2.42684,6.04679 7.6098878,7.6098878 0 0 1 -1.00849,3.78247 7.6098878,7.6098878 0 0 1 -1.5963,0.92072 7.6098878,7.6098878 0 0 1 -1.59626,0.92072 7.6098878,7.6098878 0 0 1 -2.85201,0.58884 c -13.65036,1.36931 -27.8309,8.78649 -36.37383,19.9433 4.14518,8.3272 7.21579,16.27327 8.91185,24.65187 15.54086,-3.62622 31.79047,-2.0192 45.28817,4.71176 a 7.6098878,7.6098878 0 0 1 1.71243,1.12411 7.6098878,7.6098878 0 0 1 1.84305,3.19152 7.6098878,7.6098878 0 0 1 0.45715,0.79218 7.6098878,7.6098878 0 0 1 0.58777,2.8607 7.6098878,7.6098878 0 0 1 -0.34045,1.25368 7.6098878,7.6098878 0 0 1 -0.34046,1.25368 7.6098878,7.6098878 0 0 1 -1.1386,1.71511 7.6098878,7.6098878 0 0 1 -1.1387,1.71411 7.6098878,7.6098878 0 0 1 -2.05385,0.12842 7.6098878,7.6098878 0 0 1 -1.59627,0.92073 7.6098878,7.6098878 0 0 1 -1.25582,-0.33184 7.6098878,7.6098878 0 0 1 -2.51059,-0.66378 7.6098878,7.6098878 0 0 1 -1.25572,-0.33193 c -10.93219,-5.45468 -24.04674,-6.71593 -36.1414,-3.61644 0.84257,22.7793 -7.86138,42.849 -24.91405,52.6953 -17.24869,9.95781 -39.08043,6.90761 -58.55086,-5.565 -8.96849,9.0596 -14.53598,22.16701 -15.27897,34.36191 a 7.6098878,7.6098878 0 0 1 0.11664,2.0437 7.6098878,7.6098878 0 0 1 -0.34045,1.2537 7.6098878,7.6098878 0 0 1 -0.79867,0.4604 7.6098878,7.6098878 0 0 1 -1.1387,1.7141 7.6098878,7.6098878 0 0 1 -0.79761,0.4603 7.6098878,7.6098878 0 0 1 -1.59628,0.9207 7.6098878,7.6098878 0 0 1 -2.05385,0.129 7.6098878,7.6098878 0 0 1 -2.053973,0.1289 7.6098878,7.6098878 0 0 1 -4.69933,-2.6091 7.6098878,7.6098878 0 0 1 -0.45715,-0.7922 7.6098878,7.6098878 0 0 1 -1.04491,-3.6529 7.6098878,7.6098878 0 0 1 0.34045,-1.2527 c 0.91965,-15.0554 7.652193,-29.93151 18.564363,-41.57551 -6.41905,-5.6678 -12.547323,-11.8152 -17.691493,-19.58247 -13.6403,2.06297 -26.80473,10.14937 -34.66043,21.06847 a 7.6098878,7.6098878 0 0 1 -0.34045,1.2537 7.6098878,7.6098878 0 0 1 -0.79761,0.4604 7.6098878,7.6098878 0 0 1 -5.24642,1.9721 7.6098878,7.6098878 0 0 1 -1.25572,-0.3319 7.6098878,7.6098878 0 0 1 -4.23746,-1.8104 7.6098878,7.6098878 0 0 1 -0.45714,-0.7923 7.6098878,7.6098878 0 0 1 -1.04492,-3.6539 7.6098878,7.6098878 0 0 1 -0.45822,-0.7923 7.6098878,7.6098878 0 0 1 1.00851,-3.7825 7.6098878,7.6098878 0 0 1 0.34045,-1.2536 c 6.76957,-9.3808 16.03633,-17.83431 27.10278,-23.09844 3.86662,-1.82118 8.31639,-3.09838 12.56029,-4.05121 -3.85055,-8.35079 -6.69506,-16.79143 -8.11382,-25.11222 -13.48882,-3.62722 -29.18257,-0.8993 -40.89482,6.58318 a 7.6098878,7.6098878 0 0 1 -1.59627,0.92073 7.6098878,7.6098878 0 0 1 -2.05386,0.12842 7.6098878,7.6098878 0 0 1 -2.05396,0.12843 7.6098878,7.6098878 0 0 1 -3.78033,-1.01711 7.6098878,7.6098878 0 0 1 -0.45714,-0.79226 7.6098878,7.6098878 0 0 1 -1.84348,-3.19254 7.6098878,7.6098878 0 0 1 -0.45715,-0.79225 7.6098878,7.6098878 0 0 1 0.55137,-4.57469 7.6098878,7.6098878 0 0 1 1.59627,-0.92074 7.6098878,7.6098878 0 0 1 0.34046,-1.25368 7.6098878,7.6098878 0 0 1 0.79759,-0.46035 7.6098878,7.6098878 0 0 1 0.34046,-1.25262 c 13.75648,-8.78862 31.97364,-12.37945 48.99964,-9.13551 0.14989,-11.34095 2.02912,-21.8008 6.83756,-30.5509 -11.50435,-1.63589 -22.5161,-7.66449 -28.77541,-18.50548 -10.81155,-18.72602 -3.76051,-42.52566 14.96583,-53.33667 z"
style="fill:#ff2a2a;fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" />
<path
id="path3847"
d="m 172.88153,898.78816 c 1.83132,1.05745 -6.95107,19.96031 -20.00325,42.56771 -13.05188,22.60654 -25.3621,40.23658 -27.19342,39.17911 -1.83139,-1.05746 7.28157,-20.53316 20.33346,-43.13977 13.05218,-22.60659 25.03181,-39.66449 26.86321,-38.60705 z"
style="fill:#d40000;fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

+3 -3
View File
@@ -35,7 +35,7 @@
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1680"
inkscape:window-height="925"
inkscape:window-height="1005"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1"
@@ -60,14 +60,14 @@
id="layer1"
transform="translate(0,-552.36218)">
<rect
style="fill:#26e1cf;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:#b6d46f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4139"
width="1024"
height="500"
x="0"
y="552.36218" />
<path
style="fill:#26af9d;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:#a6c45f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 786.41602 28.683594 A 397.04337 397.04337 0 0 0 389.37305 425.72656 A 397.04337 397.04337 0 0 0 396.67383 500 L 1024 500 L 1024 108.21484 A 397.04337 397.04337 0 0 0 786.41602 28.683594 z "
transform="translate(0,552.36218)"
id="circle5439" />

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

+5 -5
View File
@@ -26,8 +26,8 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.7708333"
inkscape:cx="31.823204"
inkscape:zoom="1.046875"
inkscape:cx="96"
inkscape:cy="96"
inkscape:document-units="px"
inkscape:current-layer="layer1"
@@ -35,7 +35,7 @@
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1680"
inkscape:window-height="925"
inkscape:window-height="1005"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1"
@@ -64,9 +64,9 @@
cy="958.36218"
cx="96"
id="circle5439"
style="fill:#26af9d;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
style="fill:#a6c45f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
style="fill:#26e1cf;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:#b6d46f;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path5437"
cx="96"
cy="956.36218"

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

-63
View File
@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
fill="#000000"
height="24"
viewBox="0 0 24 24"
width="24"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="marker.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1215"
inkscape:window-height="643"
id="namedview8"
showgrid="false"
inkscape:showpageshadow="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 6.4576853,2.0235976 C 4.0190093,2.0235564 2.0237288,4.0188781 2.0237288,6.4575542 l 0,15.5188478 4.4339565,0 0,-15.5188478 15.5185857,0 0,-4.4336942 z"
id="rect4146"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sscccccs" />
<path
d="M0 0h24v24H0z"
fill="none"
id="path4" />
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

+47 -36
View File
@@ -1,8 +1,7 @@
#!/usr/bin/env bash
# Try to find Inkscape or ImageMagick's convert
find_converter()
{
find_converter() {
if [ -z "$INKSCAPE" ]
then
INKSCAPE=$(which inkscape) ||
@@ -11,22 +10,20 @@ find_converter()
if [ -x "$INKSCAPE" ]
then
converter()
{
converter() {
"$INKSCAPE" \
"$PWD/$1" \
-e "$PWD/$2" \
-w "$3" \
-h "$3"
-h "$4"
}
elif which convert &>/dev/null
then
converter()
{
converter() {
convert \
-background none \
"$1" \
-thumbnail "$3" \
-thumbnail "${3}x${4}" \
-strip \
"$2"
}
@@ -35,50 +32,55 @@ find_converter()
fi
}
# Scale a length and cut off fraction
#
# @param 1 - length
# @param 2 - multiplier
scale() {
echo "$1*$2" | bc -l | cut -d '.' -f 1
}
# Make sure $DIR exists
check_dir() {
[ -d "$DIR" ] || mkdir -p "$DIR" || {
echo "error: couldn't create $DIR" >&2
return $?
}
}
# Returns true if source is older than target file
#
# @param 1 - target file
# @param 2 - source file
newer_than() {
[ -r "$1" ] && [ -z "$(find "$2" -type f -newer "$1")" ]
}
# Convert SVG files in multiple resolutions to PNG
#
# @param 1 - output path
update()
{
[ "$1" ] || return 1
type converter &>/dev/null || find_converter || {
echo "error: no Inkscape and no ImageMagick convert" >&2
return 1
}
update() {
local SVG SIZE NEGATE
local DPI MULTIPLIER
local DIR PNG
while read -r SVG SIZE NEGATE
do
SIZE=${SIZE:-24}
local DPI MULTIPLIER DIR PNG
while read -r DPI MULTIPLIER
do
DIR="$1-$DPI"
[ -d "$DIR" ] || mkdir -p "$DIR" || {
echo "error: couldn't create $DIR" >&2
return $?
}
check_dir || return $?
PNG=${SVG##*/}
PNG="$DIR/${PNG%.*}.png"
# skip existing up to date files
[ -r "$PNG" ] && [ -z "$(find \
"$SVG" \
-type f \
-newer "$PNG")" ] && continue
newer_than "$PNG" "$SVG" && continue
converter \
"$SVG" \
"$PNG" \
"$(echo "$SIZE*$MULTIPLIER" |
bc -l |
cut -d '.' -f 1)"
"$(scale "${SIZE%%x*}" "$MULTIPLIER")" \
"$(scale "${SIZE##*x}" "$MULTIPLIER")"
if (( NEGATE ))
then
@@ -92,13 +94,22 @@ hdpi 1.5
mdpi 1
ldpi .75
EOF
done
done
}
update app/src/main/res/drawable << EOF
svg/marker.svg 48
type converter &>/dev/null || find_converter || {
echo "error: no Inkscape and no ImageMagick convert" >&2
exit 1
}
update app/src/debug/res/mipmap << EOF
svg/debug/ic_launcher.svg 48
EOF
update app/src/main/res/mipmap << EOF
svg/ic_launcher.svg 48
EOF
update app/src/main/res/drawable << EOF
svg/wallpaper.svg 384
EOF
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="192"
height="192"
viewBox="0 0 192 192"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="wallpaper.svg"
inkscape:export-filename="/Users/markus/app_icon.png"
inkscape:export-xdpi="240"
inkscape:export-ydpi="240">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.0833333"
inkscape:cx="96"
inkscape:cy="96"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1680"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1"
showguides="false" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-860.36218)">
<circle
r="88"
cy="882.36218"
cx="150"
id="circle4151"
style="fill:#1b1b1b;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#111111;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 151.2832,816.81726 a 64.837967,64.837967 0 0 0 -64.837888,64.83789 64.837967,64.837967 0 0 0 64.837888,64.83789 64.837967,64.837967 0 0 0 64.83789,-64.83789 64.837967,64.837967 0 0 0 -64.83789,-64.83789 z m -1.8457,31.52148 a 35.467094,35.467094 0 0 1 2.66016,0.13282 23.307953,23.307953 0 0 0 -2.01368,9.4375 23.307953,23.307953 0 0 0 23.3086,23.30664 23.307953,23.307953 0 0 0 11.07031,-2.82032 35.467094,35.467094 0 0 1 0.44141,5.41016 35.467094,35.467094 0 0 1 -35.4668,35.4668 35.467094,35.467094 0 0 1 -35.46875,-35.4668 35.467094,35.467094 0 0 1 35.46875,-35.4668 z"
id="circle4143"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB