Im trying to get the detected object from the OverlayView. This project is TensorFlow Lite.
The detected object will be displayed in the text view and then will also be broadcasted using text to speech after long press.
This is the MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
btnSpeak = findViewById(R.id.buttonbtn)
val etSpeak: TextView = findViewById(R.id.DetectedNameTxtView)
// TextToSpeech(Context: this, OnInitListener: this)
tts = TextToSpeech(this, this)
btnSpeak!!.isEnabled = false;
}
private fun speakOut() {
//set the text from the textView to lowercase
val etSpeak: TextView = findViewById(R.id.DetectedNameTxtView)
val detected = etSpeak?.text.toString().lowercase()
if (detected == null || detected == "textview"){
val noDetected = "No Object Detected";
tts!!.speak(noDetected, TextToSpeech.QUEUE_FLUSH, null,"")
} else{
Log.e("detectionMain",etSpeak.toString())
tts!!.speak(detected, TextToSpeech.QUEUE_FLUSH, null,"")
}
}
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
val result = tts!!.setLanguage(Locale.US)
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS","The Language not supported!")
} else {
btnSpeak!!.isEnabled = true
}
} else {
Log.e("TTS", "Initilization Failed!")
}
}
override fun onBackPressed() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
// Workaround for Android Q memory leak issue in IRequestFinishCallback$Stub.
// (https://issuetracker.google.com/issues/139738913)
finishAfterTransition()
} else {
super.onBackPressed()
}
}
// GestureDetecctor to detect long press
private val gestureDetector = GestureDetector(object : SimpleOnGestureListener() {
override fun onLongPress(e: MotionEvent) {
speakOut()
// Toast to notify the Long Press
Toast.makeText(applicationContext, "Long Press Detected", Toast.LENGTH_SHORT).show()
}
})
// onTouchEvent to confirm presence of Touch due to Long Press
override fun onTouchEvent(event: MotionEvent?): Boolean {
return gestureDetector.onTouchEvent(event)
}
This is the OverlayView. The string that I need to transfer is the drawabletext.
private var results: List<Detection> = LinkedList<Detection>()
override fun draw(canvas: Canvas) {
super.draw(canvas)
for (result in results) {
val boundingBox = result.boundingBox
val top = boundingBox.top * scaleFactor
val bottom = boundingBox.bottom * scaleFactor
val left = boundingBox.left * scaleFactor
val right = boundingBox.right * scaleFactor
// Draw bounding box around detected objects
val drawableRect = RectF(left, top, right, bottom)
canvas.drawRect(drawableRect, boxPaint)
// Create text to display alongside detected objects
var drawableText = result.categories[0].label
// Draw rect behind display text
textBackgroundPaint.getTextBounds(drawableText, 0, drawableText.length, bounds)
val textWidth = bounds.width()
val textHeight = bounds.height()
canvas.drawRect(
left,
top,
left + textWidth + Companion.BOUNDING_RECT_TEXT_PADDING,
top + textHeight + Companion.BOUNDING_RECT_TEXT_PADDING,
textBackgroundPaint
)
// Draw text for detected object
canvas.drawText(drawableText, left, top + bounds.height(), textPaint)
}
}
Related
I'm developing an android app with drawing functionality. I want to save those drawing Paths, so i can redraw them when I reopen the activity from history. I just want to save everything either its a path or custom shape on canvas.
private val drawShapes = Stack<ShapeAndPaint?>()
private val redoShapes = Stack<ShapeAndPaint?>()
private var currentShape: ShapeAndPaint? = null
var isDrawingEnabled = false
private set
private var viewChangeListener: BrushViewChangeListener? = null
var currentShapeBuilder: ShapeBuilder? = null
// eraser parameters
private var isErasing = false
var serializablePath = SerializablePath()
var customPath = CustomPath()
// endregion
#SuppressLint("Range")
private fun createPaint(): Paint {
val paint = Paint()
paint.isAntiAlias = true
paint.isDither = true
paint.style = Paint.Style.STROKE
paint.strokeJoin = Paint.Join.ROUND
paint.strokeCap = Paint.Cap.ROUND
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
// apply shape builder parameters
currentShapeBuilder?.apply {
paint.strokeWidth = this.shapeSize
paint.alpha = this.shapeOpacity
paint.color = this.shapeColor
}
if (WhiteBoardActivity.dullPenSelected){
paint.alpha = 40
}
return paint
}
private fun createEraserPaint(): Paint {
val paint = createPaint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
return paint
}
private fun setupBrushDrawing() {
//Caution: This line is to disable hardware acceleration to make eraser feature work properly
setLayerType(LAYER_TYPE_HARDWARE, null)
visibility = GONE
currentShapeBuilder = ShapeBuilder()
}
fun clearAll() {
drawShapes.clear()
redoShapes.clear()
invalidate()
}
fun setBrushViewChangeListener(brushViewChangeListener: BrushViewChangeListener?) {
viewChangeListener = brushViewChangeListener
}
public override fun onDraw(canvas: Canvas) {
for (shape in drawShapes) {
shape?.shape?.draw(canvas, shape.paint)
}
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
return if (isDrawingEnabled) {
val touchX = event.x
val touchY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> onTouchEventDown(touchX, touchY)
MotionEvent.ACTION_MOVE -> onTouchEventMove(touchX, touchY)
MotionEvent.ACTION_UP -> onTouchEventUp(touchX, touchY)
}
invalidate()
true
} else {
false
}
}
private fun onTouchEventDown(touchX: Float, touchY: Float) {
createShape()
currentShape?.shape?.startShape(touchX, touchY)
customPath.ActionLine(touchX,touchY)
}
private fun onTouchEventMove(touchX: Float, touchY: Float) {
currentShape?.shape?.moveShape(touchX, touchY)
customPath.ActionMove(touchX,touchY)
}
private fun onTouchEventUp(touchX: Float, touchY: Float) {
currentShape?.apply {
shape.stopShape()
endShape(touchX, touchY)
}
}
private fun createShape() {
var paint = createPaint()
var shape: AbstractShape = BrushShape()
if (isErasing) {
paint = createEraserPaint()
} else {
when(currentShapeBuilder?.shapeType){
ShapeType.BRUSH -> {
shape = BrushShape()
}
else -> {}
}
}
currentShape = ShapeAndPaint(shape, paint)
drawShapes.push(currentShape)
viewChangeListener?.onStartDrawing()
}
private fun endShape(touchX: Float, touchY: Float) {
if (currentShape?.shape?.hasBeenTapped() == true) {
// just a tap, this is not a shape, so remove it
drawShapes.remove(currentShape)
//handleTap(touchX, touchY);
}
viewChangeListener?.apply {
onStopDrawing()
onViewAdd(this#DrawingView)
}
}
fun undo(): Boolean {
if (!drawShapes.empty()) {
redoShapes.push(drawShapes.pop())
invalidate()
}
viewChangeListener?.onViewRemoved(this)
return !drawShapes.empty()
}
fun redo(): Boolean {
if (!redoShapes.empty()) {
drawShapes.push(redoShapes.pop())
invalidate()
}
viewChangeListener?.onViewAdd(this)
return !redoShapes.empty()
}
// region eraser
fun brushEraser() {
isDrawingEnabled = true
isErasing = true
}
// endregion
// region Setters/Getters
fun enableDrawing(brushDrawMode: Boolean) {
isDrawingEnabled = brushDrawMode
isErasing = !brushDrawMode
if (brushDrawMode) {
visibility = VISIBLE
}
}
fun getBitmap(): Bitmap? {
destroyDrawingCache()
this.buildDrawingCache()
return this.drawingCache
}
fun setPaintAlpha(i: Int) {
createPaint().alpha = i
}
// endregion
val drawingPath: Pair<Stack<ShapeAndPaint?>, Stack<ShapeAndPaint?>>
get() = Pair(drawShapes, redoShapes)
companion object {
private const val DEFAULT_ERASER_SIZE = 50.0f
var eraserSize = DEFAULT_ERASER_SIZE
}
// region constructors
init {
setupBrushDrawing()
}
}
this is my DrawingView class.
is there any possible solution? Thanks in advance.
I am trying to build a realtime text recognization app using google MLKit vision, it's showing text properly but when I am trying to click on a particular line it is only showing the last line text.
Here is Overlay Code :
TextGraphic.kt
class TextGraphic(overlay: GraphicOverlay?,
private val element: Text.Line,
font: Typeface,
fontSize: Float,
color: Int) : Graphic(overlay!!) {
private val rectPaint: Paint = Paint()
private val textPaint: Paint
override fun draw(canvas: Canvas?) {
val rect = RectF(element.boundingBox)
canvas!!.drawRect(rect, rectPaint)
canvas.drawText(element.text, rect.left, rect.bottom, textPaint)
}
companion object {
private const val TAG = "TextGraphic"
private const val TEXT_COLOR = Color.BLACK
private const val STROKE_WIDTH = 2.0f
}
init {
rectPaint.color = color
rectPaint.style = Paint.Style.FILL_AND_STROKE
rectPaint.strokeWidth = STROKE_WIDTH
textPaint = Paint()
textPaint.color = TEXT_COLOR
textPaint.textSize = fontSize
textPaint.typeface = font
postInvalidate()
}}
GraphicOverlay.kt
class GraphicOverlay(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val lock = Any()
private var previewWidth = 0
private var widthScaleFactor = 1.0f
private var previewHeight = 0
private var heightScaleFactor = 1.0f
private var facing = CameraCharacteristics.LENS_FACING_BACK
private val graphics: MutableSet<Graphic> = HashSet()
abstract class Graphic(private val overlay: GraphicOverlay) {
abstract fun draw(canvas: Canvas?)
fun scaleX(horizontal: Float): Float {
return horizontal * overlay.widthScaleFactor
}
fun scaleY(vertical: Float): Float {
return vertical * overlay.heightScaleFactor
}
val applicationContext: Context
get() = overlay.context.applicationContext
fun translateX(x: Float): Float {
return if (overlay.facing == CameraCharacteristics.LENS_FACING_FRONT) {
overlay.width - scaleX(x)
} else {
scaleX(x)
}
}
fun translateY(y: Float): Float {
return scaleY(y)
}
fun postInvalidate() {
overlay.postInvalidate()
}
}
fun clear() {
synchronized(lock) { graphics.clear() }
postInvalidate()
}
fun add(graphic: Graphic) {
synchronized(lock) { graphics.add(graphic) }
postInvalidate()
}
fun remove(graphic: Graphic) {
synchronized(lock) { graphics.remove(graphic) }
postInvalidate()
}
fun setCameraInfo(previewWidth: Int, previewHeight: Int, facing: Int) {
synchronized(lock) {
this.previewWidth = previewWidth
this.previewHeight = previewHeight
this.facing = facing
}
postInvalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
synchronized(lock) {
if (previewWidth != 0 && previewHeight != 0) {
widthScaleFactor = width.toFloat() / previewWidth.toFloat()
heightScaleFactor = height.toFloat() / previewHeight.toFloat()
}
for (graphic in graphics) {
graphic.draw(canvas)
}
}
}}
Inside my Fragment where I am clicking :
private fun processTextFromImage(visionText: Text, imageProxy: ImageProxy) {
binding.graphicOverlay.clear()
for (block in visionText.textBlocks) {
for (line in block.lines) {
val textGraphic = TextGraphic(binding.graphicOverlay, line, font, fontSize, color = fontColor)
binding.graphicOverlay.apply {
add(textGraphic)
setOnClickListener {
Toast.makeText(it.context, line.text, Toast.LENGTH_SHORT).show()
}
}
for (element in line.elements) {
textFoundListener(element.text)
}
}
}
}
Is there any better way to to display overlay, this overlay is too fast and my click is only displays the last line text.
If anyone can help me in this, thanks a lot.
Android graphview load widget using androidplot library in build gradle
implementation "com.androidplot:androidplot-core:1.5.7"
Dynamicaally create chart using below code getresponse based from network call.
In class file my sample code...
val plot = XYPlot(context, "")
val h = context.resources.getDimension(R.dimen.sample_widget_height).toInt()
val w = context.resources.getDimension(R.dimen.sample_widget_width).toInt()
plot.graph.setMargins(100f, 0f, 0f, 16f)
plot.graph.setPadding(0f, 0f, 0f, 0f)
plot.graph.gridInsets.left = 100f
plot.graph.gridInsets.top = 0f
plot.graph.gridInsets.right = 0f
plot.graph.gridInsets.bottom = 40f
plot.legend.textPaint.textSize = 1f
plot.linesPerRangeLabel = 8
plot.linesPerDomainLabel = 8
plot.graph.setSize(Size.FILL)
plot.measure(w, h);
plot.layout(0, 0, w, h);
plot.graph.position(0f, HorizontalPositioning.ABSOLUTE_FROM_LEFT, 0f,
VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.LEFT_TOP)
val series1Numbers = mutableListOf<Int>()
val series2Numbers = mutableListOf<Int>()
val xLabels = mutableListOf<String>()
for (i in 0 until model.Details.Items.size) {
var item = model.Details.Items[i]
series1Numbers.add(item.TotalScore)
series2Numbers.add(0)
xLabels.add(item.Date)
}
val series1 = SimpleXYSeries(series1Numbers, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series1")
val series2 = SimpleXYSeries(series2Numbers, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series1")
val series1Format = LineAndPointFormatter(context, R.xml.line_point_formatter_with_labels)
val series2Format = LineAndPointFormatter(context,
R.xml.line_point_formatter_with_labels_2)
plot.graph.setLineLabelEdges(XYGraphWidget.Edge.LEFT, XYGraphWidget.Edge.BOTTOM)
plot.setRangeBoundaries(-110, 110, BoundaryMode.FIXED)
plot.graph.getLineLabelStyle(XYGraphWidget.Edge.LEFT).format = object : Format() {
override fun format(obj: Any, toAppendTo:
StringBuffer, pos: FieldPosition): StringBuffer {
val i = Math.round((obj as Number).toFloat())
L.m("widget y axos label value ", i.toString())
plot.graph.setLineLabelRenderer(XYGraphWidget.Edge.LEFT, MyLineLabelRenderer())
return if (i > 50) {
return toAppendTo.append(context.getString(R.string.str_excellent))
} else if (i > 25 && i <= 50) {
return toAppendTo.append(context.getString(R.string.str_very_good))
} else if (i > 0 && i <= 25) {
return toAppendTo.append(context.getString(R.string.str_good))
} else if (i == 0) {
return toAppendTo.append(context.getString(R.string.str_neutral))
} else if (i < 0 && i >= -25) {
return toAppendTo.append(context.getString(R.string.str_not_good))
} else if (i < -25 && i >= -50) {
return toAppendTo.append(context.getString(R.string.str_be_aware))
} else if (i < -50) {
return toAppendTo.append(context.getString(R.string.str_time_out))
} else {
return toAppendTo.append(context.getString(R.string.str_neutral))
}
}
override fun parseObject(source: String, pos: ParsePosition): Any {
// unused
return ""
}
}
plot.legend.setVisible(false)
plot.getGraph().linesPerDomainLabel = 5
plot.getGraph().linesPerRangeLabel = 5
plot.graph.getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).format = object : Format() {
override fun format(obj: Any, #NonNull toAppendTo:
StringBuffer, #NonNull pos: FieldPosition): StringBuffer {
val i = Math.round((obj as Number).toFloat())
val displayFormat = SimpleDateFormat(prefs.selectedTimeFormat, Locale.US)
val originalFormat = SimpleDateFormat(Constants.DATE_yyyyMMddHHmmss, Locale.US)
var displayTime = ""
if (prefs.selectedTimeFormat.equals(Constants.TWENTYFOUR_HOUR_FORMAT))
displayTime = displayFormat.format(originalFormat.parse(xLabels.get(i)))
else {
displayTime = displayFormat.format(originalFormat.parse(xLabels.get(i)))
displayTime.replace("AM", " am")
displayTime.replace("PM", " pm")
}
plot.graph.setLineLabelRenderer(XYGraphWidget.Edge.BOTTOM, MyLineLabelRenderer())
return toAppendTo.append(displayTime)
}
override fun parseObject(source: String, #NonNull pos: ParsePosition): Any {
return ""
}
}
plot.addSeries(series1, series1Format)
plot.addSeries(series2, series2Format)
val bitmap: Bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
plot.draw(Canvas(bitmap))
remoteViews!!.setImageViewBitmap(R.id.img_graph_view, bitmap)
appWidgetManager!!.updateAppWidget(appWidgetId, remoteViews)
I am tried to reduce bottom label shown text size is not working it's overlapping one another
How to reduce bottom label text size?
setTextsize used below code
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.LEFT).getPaint().textSize = 16f
I get strange behaviour when trying to render some text along with a number. It is likely that the problem is related to threading. My problem is that when I draw with Canvas.drawText() the number is always displayed as 0.0 but if I Log.d() the number is not 0.0 but rather around ~60.
Here is my code:
GameView:
class GameView(
context: Context?,
attrs: AttributeSet
) : SurfaceView(context, attrs), SurfaceHolder.Callback {
private var gameLoop: GameLoop = GameLoop(this, holder)
private val paint: Paint = Paint()
init {
holder.addCallback(this)
isFocusable = true
}
fun update() {
// TODO: update game state
}
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
gameLoop.startLoop()
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
// do nothing
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
gameLoop.stopLoop()
}
override fun draw(drawCanvas: Canvas) {
super.draw(drawCanvas)
drawInfo(drawCanvas)
}
private fun drawInfo(canvas: Canvas) {
val averageUps = gameLoop.getAverageUps()
val averageFps = gameLoop.getAverageFps()
val color = ContextCompat.getColor(context, DEBUG_TEXT_COLOR)
paint.color = color
paint.textSize = DEBUG_TEXT_SIZE
canvas.drawText("UPS: $averageUps", 40f, DEBUG_TEXT_SIZE, paint)
canvas.drawText("FPS: $averageFps", 40f, DEBUG_TEXT_SIZE*2 + 10f, paint)
}
companion object {
const val DEBUG_TEXT_SIZE = 40f
const val DEBUG_TEXT_COLOR = R.color.light_blue_A200
}
}
GameLoop:
class GameLoop(
private val gameView: GameView,
private val surfaceHolder: SurfaceHolder
) : Thread() {
private var isRunning: Boolean = false
private var averageUps: Double = 0.0
private var averageFps: Double = 0.0
fun getAverageUps(): Double {
return averageUps
}
fun getAverageFps(): Double {
return averageFps
}
fun startLoop() {
isRunning = true
start()
}
override fun run() {
super.run()
var updateCount = 0
var frameCount = 0
var startTime = 0L
var elapsedTime = 0L
var sleepTime = 0L
var canvas: Canvas? = null
startTime = System.currentTimeMillis()
while (isRunning) {
try {
canvas = surfaceHolder.lockCanvas()
synchronized(canvas) {
gameView.update()
updateCount++
gameView.draw(canvas)
}
} catch (iae: IllegalArgumentException) {
iae.printStackTrace()
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas)
frameCount++
}
}
elapsedTime = System.currentTimeMillis() - startTime
sleepTime = (updateCount*UPS_PERIOD - elapsedTime)
if (sleepTime > 0) {
try {
sleep(sleepTime)
} catch (ie: InterruptedException) {
ie.printStackTrace()
}
}
while (sleepTime < 0 && updateCount < MAX_UPS-1) {
gameView.update()
updateCount++
elapsedTime = System.currentTimeMillis() - startTime
sleepTime = (updateCount*UPS_PERIOD - elapsedTime)
}
elapsedTime = System.currentTimeMillis() - startTime
if (elapsedTime >= 1000.0) {
averageUps = updateCount.toDouble() / (elapsedTime/1000.0)
averageFps = frameCount.toDouble() / (elapsedTime/1000.0)
updateCount = 0
frameCount = 0
startTime = System.currentTimeMillis()
}
}
}
fun stopLoop() {
// TODO: finish me...
}
companion object {
private const val MAX_UPS: Long = 60
const val UPS_PERIOD: Long = 1000/MAX_UPS
}
}
The problem is in GameView.drawInfo(canvas: Canvas). Output is always "UPS: 0.0 FPS: 0.0" but when I log out averageUps and averageFps they are both around ~60.
I hope that you can help me resolve this issue :-)
To answer my own question:
The problem was in the activity that contains GameView.
I had GameView in the xml.
When i instantiated the GameView manually and sat it with setContentView(gameView) all rendering started to work.
I have successfully achieved saving to the gallery after I finish drawing on canvas, but now I need to open an image from galley ( for example my old drawing) and use it as a background, after some searching there no answer which can help me. I already started some attempts to do this, but any advice or solutions how I can achieve it will be great.
There is my custom view with Canvas:
(Permission for READ and WRITE is already granted and handled in another class)
var drawingColor: Int = ResourcesCompat.getColor(resources, R.color.colorBlack, null)
var strokeDrawWidth: Float = 12f
private var path = Path()
private val paths = ArrayList<Triple<Path, Int, Float>>()
private val undonePaths = ArrayList<Triple<Path, Int, Float>>()
private val extraCanvas: Canvas? = null
private var bitmapBackground: Bitmap? = null
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
private var currentX = 0f
private var currentY = 0f
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private val paint = Paint().apply {
color = drawingColor
isAntiAlias = true
isDither = true
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
strokeWidth = strokeDrawWidth
}
fun loadCanvasBackground(bitmap: Bitmap) {
bitmapBackground = bitmap
invalidate()
}
fun saveCanvasDrawing() {
canvasCustomView.isDrawingCacheEnabled = true
val extraBitmap: Bitmap = canvasCustomView.drawingCache
MediaStore.Images.Media.insertImage(context.contentResolver, extraBitmap, "drawing", "Paint R")
}
fun resetCanvasDrawing() {
path.reset()
paths.clear()
invalidate()
}
fun undoCanvasDrawing() {
if (paths.size > 0) {
undonePaths.add(paths.removeAt(paths.size - 1))
invalidate()
} else {
Log.d("UNDO_ERROR", "Something went wrong with UNDO action")
}
}
fun redoCanvasDrawing() {
if (undonePaths.size > 0) {
paths.add(undonePaths.removeAt(undonePaths.size - 1))
invalidate()
} else {
Log.d("REDO_ERROR", "Something went wrong with REDO action")
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (bitmapBackground != null) {
extraCanvas?.drawBitmap(bitmapBackground!!, 0f, 0f, paint)
}
for (p in paths) {
paint.strokeWidth = p.third
paint.color = p.second
canvas?.drawPath(p.first, paint)
}
paint.color = drawingColor
paint.strokeWidth = strokeDrawWidth
canvas?.drawPath(path, paint)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event == null)
return false
motionTouchEventX = event.x
motionTouchEventY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
undonePaths.clear()
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
invalidate()
}
MotionEvent.ACTION_MOVE -> {
val distanceX = abs(motionTouchEventX - currentX)
val distanceY = abs(motionTouchEventY - currentY)
if (distanceX >= touchTolerance || distanceY >= touchTolerance) {
path.quadTo(
currentX,
currentY,
(motionTouchEventX + currentX) / 2,
(currentY + motionTouchEventY) / 2
)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
invalidate()
}
MotionEvent.ACTION_UP -> {
path.lineTo(currentX, currentY)
extraCanvas?.drawPath(path, paint)
paths.add(Triple(path, drawingColor, strokeDrawWidth))
path = Path()
}
}
return true
}
override fun isSaveEnabled(): Boolean {
return true
}
So no-one answered and after some searching and experiments I get working solution, use it or adapt for your need if you will get in the same situation
( answer is based on the code from my question - so if you miss some dependencies please check it )
Init companion object with request code for gallery action in your ACTIVITY
companion object {
private const val GALLERY_REQUEST_CODE = 102
}
Create a method to pick image from gallery ( you need to receive Uri )
private fun pickFromGallery() {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
val imageTypes = arrayOf("image/jpeg", "image/png")
intent.putExtra(Intent.EXTRA_MIME_TYPES, imageTypes)
startActivityForResult(intent, GALLERY_REQUEST_CODE)
}
Than you need to override onActivityResult() method to receive your Uri and send it to the custom view
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == GALLERY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
val uri: Uri? = data?.data
if (uri != null) {
val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, uri)
canvasCustomView.loadCanvasBackground(bitmap)
}
}
}
}
Now in onDraw() method ( in your Custom View ) you need to use .drawBitmap to set your received Uri aka bitmap as a background to your canvas
override fun onDraw(canvas: Canvas?) {
if (bitmapBackground != null) {
canvas?.drawBitmap(bitmapBackground!!, 0f, 0f, paint)
}