Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e02420d446 | ||
|
|
e63fe0d601 | ||
|
|
0803fdcac8 | ||
|
|
e64dd2e972 | ||
|
|
418edd34a5 | ||
|
|
af2a1dc940 | ||
|
|
e0c420c29f | ||
|
|
0ba41ca863 | ||
|
|
ed1826679e | ||
|
|
bd75b5b71d | ||
|
|
bbdf724bbb | ||
|
|
0ace08c8b4 | ||
|
|
41164c31f1 | ||
|
|
9f3288e7a8 | ||
|
|
a61fc63fa6 | ||
|
|
26a19a9657 | ||
|
|
a0d157551a | ||
|
|
53fe8b3689 | ||
|
|
0da9e34e55 | ||
|
|
db276372b7 | ||
|
|
c148635c26 | ||
|
|
5c99309125 | ||
|
|
2443eb066a | ||
|
|
d7281266e4 | ||
|
|
e8c676ab8c | ||
|
|
6c1353596d | ||
|
|
ff14130c91 | ||
|
|
e0147df76c | ||
|
|
cbc2b6b8db | ||
|
|
bc727ab45f | ||
|
|
9e8153e8f6 | ||
|
|
b9a5687c5a | ||
|
|
cbc0ff435f | ||
|
|
3a650bc0cd | ||
|
|
cbcc9591ff | ||
|
|
7578d9b217 | ||
|
|
7789c21a96 | ||
|
|
366548dc2a | ||
|
|
2324c18807 | ||
|
|
e7e42694bc | ||
|
|
981fb2f0d8 | ||
|
|
90036e1ba2 | ||
|
|
f0916d55ba | ||
|
|
07177a0c70 | ||
|
|
478e8930a8 | ||
|
|
2d70b64dd1 | ||
|
|
25173bd7c7 | ||
|
|
9adb5174af | ||
|
|
5f0c00cd55 |
@@ -0,0 +1,18 @@
|
||||
language: android
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- build-tools-26.0.0
|
||||
- android-26
|
||||
- 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
|
||||
@@ -0,0 +1,25 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
@@ -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.
|
||||
@@ -16,6 +16,9 @@ release: lint
|
||||
lint:
|
||||
./gradlew lintDebug
|
||||
|
||||
sonarqube:
|
||||
./gradlew sonarqube
|
||||
|
||||
infer: clean
|
||||
infer -- ./gradlew assembleDebug
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ android {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 1
|
||||
versionName '1.0.0'
|
||||
versionCode 6
|
||||
versionName '1.3.0'
|
||||
|
||||
renderscriptTargetApi 24
|
||||
renderscriptSupportModeEnabled true
|
||||
@@ -27,6 +27,9 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
proguardFiles \
|
||||
getDefaultProguardFile('proguard-android.txt'), \
|
||||
'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-dontwarn com.google.**
|
||||
-dontwarn android.support.v8.**
|
||||
|
||||
-keep class android.support.v8.renderscript.** { *; }
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -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,24 @@ 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)
|
||||
val intent = getIntent()
|
||||
var fragment: Fragment
|
||||
if (intent != null && intent.hasExtra(ENCODE)) {
|
||||
fragment = EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE))
|
||||
} else if (intent != null && intent.hasExtra(DECODE)) {
|
||||
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 {
|
||||
|
||||
|
Before Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 597 B |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 799 B |
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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,4 +4,7 @@
|
||||
<color name="primary_dark">#111</color>
|
||||
<color name="primary_dark_translucent">#8111</color>
|
||||
<color name="accent">#26e1cf</color>
|
||||
<color name="accent_dark">#16d1bf</color>
|
||||
<color name="background_color">#111</color>
|
||||
<color name="button_text">#fff</color>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.4-2'
|
||||
ext.kotlin_version = '1.1.51'
|
||||
ext.tools_version = '2.3.3'
|
||||
ext.build_tools_version = '26.0.0'
|
||||
ext.sdk_version = 26
|
||||
@@ -20,6 +20,10 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.5'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
|
||||
@@ -18,8 +18,8 @@ android {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 2
|
||||
versionName '1.0.1'
|
||||
versionCode 5
|
||||
versionName '1.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ 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);
|
||||
}
|
||||
@@ -56,18 +58,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);
|
||||
}
|
||||
@@ -87,12 +116,12 @@ public class CameraView extends FrameLayout {
|
||||
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;
|
||||
@@ -102,6 +131,10 @@ public class CameraView extends FrameLayout {
|
||||
@Override
|
||||
protected void onPostExecute(Camera camera) {
|
||||
if (camera == null) {
|
||||
if (onCameraListener != null &&
|
||||
CameraView.this.camera == null) {
|
||||
onCameraListener.onCameraError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
CameraView.this.camera = camera;
|
||||
@@ -151,6 +184,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 +245,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 +293,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 +312,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 +332,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 +345,7 @@ public class CameraView extends FrameLayout {
|
||||
holder.addCallback(new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
// wait until the surface has dimensions
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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="3.7708333"
|
||||
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="925"
|
||||
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:#26af9d;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"
|
||||
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 |
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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 |