1 package jp.osdn.gokigen.gokigenassets.liveview
3 import android.content.Context
4 import android.content.res.Configuration
5 import android.graphics.*
6 import android.os.Looper
7 import android.util.AttributeSet
8 import android.util.DisplayMetrics
9 import android.util.Log
10 import android.util.TypedValue
11 import android.view.MotionEvent
12 import android.view.View
13 import android.widget.ImageView
14 import android.widget.SeekBar
15 import android.widget.SeekBar.OnSeekBarChangeListener
16 import jp.osdn.gokigen.gokigenassets.camera.interfaces.ICameraControl
17 import jp.osdn.gokigen.gokigenassets.camera.interfaces.IFocusingModeNotify
18 import jp.osdn.gokigen.gokigenassets.constants.IApplicationConstantConvert.Companion.ID_DRAWABLE_BACKGROUND_IMAGE
19 import jp.osdn.gokigen.gokigenassets.constants.IApplicationConstantConvert.Companion.MAX_VALUE_SEEKBAR
20 import jp.osdn.gokigen.gokigenassets.liveview.focusframe.IAutoFocusFrameDisplay
21 import jp.osdn.gokigen.gokigenassets.liveview.focusframe.IFocusFrameDrawer
22 import jp.osdn.gokigen.gokigenassets.liveview.gridframe.GridFrameFactory
23 import jp.osdn.gokigen.gokigenassets.liveview.gridframe.IGridFrameDrawer
24 import jp.osdn.gokigen.gokigenassets.liveview.gridframe.IShowGridFrame
25 import jp.osdn.gokigen.gokigenassets.liveview.image.IImageProvider
26 import jp.osdn.gokigen.gokigenassets.liveview.message.IMessageDrawer
27 import jp.osdn.gokigen.gokigenassets.liveview.message.InformationDrawer
29 import kotlin.math.min
31 class LiveImageView : View, ILiveView, ILiveViewRefresher, IShowGridFrame, OnSeekBarChangeListener, IFocusingModeNotify, IFocusFrameDrawer, IAutoFocusFrameDisplay, ICachePositionProvider
35 private val TAG = LiveImageView::class.java.simpleName
38 private var sliderPosition : Float = 0.0f
39 private var imageRotationDegrees : Int = 0
40 private var showGrid : Boolean = false
41 private var showingFocusFrame = false
42 private val imageScaleType: ImageView.ScaleType = ImageView.ScaleType.FIT_CENTER
43 private lateinit var imageBitmap: Bitmap
44 private var focusFrameStatus: IAutoFocusFrameDisplay.FocusFrameStatus = IAutoFocusFrameDisplay.FocusFrameStatus.None
45 private lateinit var imageProvider : IImageProvider
46 private lateinit var gridFrameDrawer : IGridFrameDrawer
47 private lateinit var informationDrawer : InformationDrawer
48 private lateinit var indicatorControl: IndicatorControl
49 private var focusFrameRect: RectF? = null
50 private var focusFrameHideTimer: Timer? = null
51 private var isRotationImage = false
53 constructor(context: Context) : super(context)
55 initComponent(context)
58 constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
60 initComponent(context)
63 constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
65 initComponent(context)
68 constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
70 initComponent(context)
73 private fun initComponent(context: Context)
75 gridFrameDrawer = GridFrameFactory().getGridFrameDrawer(0)
76 //focusFrameDrawer = FocusFrameDrawer(context)
77 informationDrawer = InformationDrawer(this)
78 indicatorControl = IndicatorControl()
79 imageBitmap = BitmapFactory.decodeResource(context.resources, ID_DRAWABLE_BACKGROUND_IMAGE)
82 fun injectDisplay(cameraControl: ICameraControl)
84 isRotationImage = cameraControl.needRotateImage()
85 cameraControl.getDisplayInjector()?.injectDisplay(this, indicatorControl, this)
88 override fun getMessageDrawer() : IMessageDrawer
90 return (informationDrawer)
93 override fun refresh()
98 override fun setImageProvider(provider: IImageProvider)
100 this.imageProvider = provider
103 override fun updateImageRotation(degrees: Int)
105 this.imageRotationDegrees = degrees
109 override fun onDraw(canvas: Canvas?)
112 if ((canvas == null)||(!(::imageProvider.isInitialized)))
114 Log.v(TAG, " ===== onDraw : canvas is not ready. ==== ")
117 //Log.v(TAG, " ----- onDraw() ----- ")
118 canvas.drawARGB(255, 0, 0, 0)
119 val imageRectF = drawImage(canvas)
122 gridFrameDrawer.drawFramingGrid(canvas, imageRectF)
124 this.drawFocusFrame(canvas,imageRectF.width(), imageRectF.height())
125 informationDrawer.drawInformationMessages(canvas, imageRectF)
126 informationDrawer.drawLevelGauge(canvas, imageRotationDegrees)
129 override fun showGridFrame(isShowGrid: Boolean)
131 this.showGrid = isShowGrid
135 private fun drawImage(canvas: Canvas) : RectF
137 val centerX = canvas.width / 2
138 val centerY = canvas.height / 2
140 val paint = Paint(Color.LTGRAY)
141 paint.strokeWidth = 1.0f
142 paint.style = Paint.Style.STROKE
144 imageBitmap = imageProvider.getImage(sliderPosition)
151 val config = context.resources.configuration
152 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE)
162 val degrees = imageRotationDegrees + addDegrees
163 val viewRect = decideViewRect(canvas, imageBitmap, degrees)
164 val width : Int = imageBitmap.width
165 val height : Int = imageBitmap.height
166 val imageRect = Rect(0, 0, width, height)
168 //Log.v(TAG, " canvas: ${canvas.width} x ${canvas.height} (D: ${rotationDegrees}) ")
169 //Log.v(TAG, " bitmap: ${imageBitmap.width} x ${imageBitmap.height} (D: ${rotationDegrees}) ")
170 //Log.v(TAG, " imageRect: [${imageRect.left},${imageRect.top}]-[${imageRect.right},${imageRect.bottom}] ")
171 //Log.v(TAG, " viewRect: [${viewRect.left},${viewRect.top}]-[${viewRect.right},${viewRect.bottom}] ")
173 canvas.rotate(degrees.toFloat(), centerX.toFloat(), centerY.toFloat())
174 canvas.drawBitmap(imageBitmap, imageRect, viewRect, paint)
175 canvas.rotate(-degrees.toFloat(), centerX.toFloat(), centerY.toFloat())
180 private fun refreshCanvas()
182 //Log.v(TAG, " refreshCanvas()")
183 if (Looper.getMainLooper().thread === Thread.currentThread())
193 private fun decideViewRect(canvas: Canvas, bitmapToShow: Bitmap?, rotationDegrees: Int): RectF
195 if (bitmapToShow == null)
197 return (RectF(0.0f, 0.0f, 1.0f, 1.0f))
201 if ((rotationDegrees == 0)||(rotationDegrees == 180))
203 srcWidth = bitmapToShow.width
204 srcHeight = bitmapToShow.height
208 srcWidth = bitmapToShow.height
209 srcHeight = bitmapToShow.width
211 val maxWidth = canvas.width
212 val maxHeight = canvas.height
213 val centerX = canvas.width / 2
214 val centerY = canvas.height / 2
215 val widthRatio = maxWidth / srcWidth.toFloat()
216 val heightRatio = maxHeight / srcHeight.toFloat()
217 val smallRatio = min(widthRatio, heightRatio)
220 if (widthRatio < heightRatio)
222 dstWidth = maxWidth.toFloat()
223 dstHeight = (smallRatio * srcHeight)
227 dstHeight = maxHeight.toFloat()
228 dstWidth = (smallRatio * srcWidth)
230 val halfWidth = dstWidth * 0.5f
231 val halfHeight = dstHeight * 0.5f
232 return (if (rotationDegrees == 0 || rotationDegrees == 180)
235 (centerX - halfWidth),
236 (centerY - halfHeight),
237 ((centerX - halfWidth) + dstWidth),
238 ((centerY - halfHeight) + dstHeight)
244 (centerX - halfHeight),
245 (centerY - halfWidth),
246 ((centerX - halfHeight) + dstHeight),
247 ((centerY - halfWidth) + dstWidth)
252 override fun getCachePosition(): Float
254 //Log.v(TAG, " ----- sliderPosition : $sliderPosition")
255 return (sliderPosition)
258 override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean)
260 Log.v(TAG, " onProgressChanged() Progress: $progress (fromUser:$fromUser)")
261 sliderPosition = (((progress).toFloat()) / ((MAX_VALUE_SEEKBAR).toFloat()))
265 override fun onStartTrackingTouch(seekBar: SeekBar?)
267 Log.v(TAG, " onStartTrackingTouch() ")
270 override fun onStopTrackingTouch(seekBar: SeekBar?)
272 Log.v(TAG, " onStopTrackingTouch() ")
275 override fun changedFocusingMode()
277 Log.v(TAG, " changedFocusingMode()")
280 override fun drawFocusFrame(canvas: Canvas, imageWidth : Float, imageHeight : Float)
283 val focusRectOnImage: RectF = convertRectOnViewfinderIntoLiveImage(focusFrameRect, imageWidth, imageHeight, imageRotationDegrees)
284 val focusRectOnView: RectF = convertRectFromImageArea(focusRectOnImage)
286 // Draw a rectangle to the canvas.
287 // Draw a rectangle to the canvas.
288 val focusFramePaint = Paint()
289 focusFramePaint.style = Paint.Style.STROKE
290 when (focusFrameStatus)
292 IAutoFocusFrameDisplay.FocusFrameStatus.Running -> focusFramePaint.color = Color.WHITE
293 IAutoFocusFrameDisplay.FocusFrameStatus.Focused -> focusFramePaint.color = Color.GREEN
294 IAutoFocusFrameDisplay.FocusFrameStatus.Failed -> focusFramePaint.color = Color.RED
295 IAutoFocusFrameDisplay.FocusFrameStatus.Errored -> focusFramePaint.color = Color.YELLOW
296 else -> focusFramePaint.color = Color.BLACK
298 val focusFrameStrokeWidth = 2.0f
299 val dm: DisplayMetrics = context.resources.displayMetrics
300 val strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, focusFrameStrokeWidth, dm)
301 focusFramePaint.strokeWidth = strokeWidth
303 canvas.drawRect(focusRectOnView, focusFramePaint)
306 override fun getContentSizeWidth(): Float
308 return (imageBitmap.width).toFloat()
311 override fun getContentSizeHeight(): Float
313 return (imageBitmap.height).toFloat()
316 override fun getPointWithEvent(event: MotionEvent?): PointF?
323 val pointOnView = PointF(event.x - x, event.y - y) // Viewの表示位置に補正
324 val pointOnImage: PointF = convertPointFromViewArea(pointOnView)
325 val imageWidth: Float
326 val imageHeight: Float
327 if (imageRotationDegrees == 0 || imageRotationDegrees == 180)
329 imageWidth = imageBitmap.width.toFloat()
330 imageHeight = imageBitmap.height.toFloat()
334 imageWidth = imageBitmap.height.toFloat()
335 imageHeight = imageBitmap.width.toFloat()
337 return convertPointOnLiveImageIntoViewfinder(
345 override fun isContainsPoint(point: PointF?): Boolean
347 return point != null && RectF(0.0f, 0.0f, 1.0f, 1.0f).contains(point.x, point.y)
350 override fun showFocusFrame(rect : RectF?, status : IAutoFocusFrameDisplay.FocusFrameStatus, duration : Float)
354 if (focusFrameHideTimer != null) {
355 focusFrameHideTimer?.cancel()
356 focusFrameHideTimer = null
359 showingFocusFrame = true
360 focusFrameStatus = status
361 focusFrameRect = rect
366 focusFrameHideTimer = Timer()
367 focusFrameHideTimer?.schedule(object : TimerTask() {
371 }, (duration * 1000).toLong())
374 catch (e : Exception)
380 override fun hideFocusFrame()
384 if (focusFrameHideTimer != null)
386 focusFrameHideTimer?.cancel()
387 focusFrameHideTimer = null
389 showingFocusFrame = false
392 catch (e : Exception)
398 private fun convertRectOnViewfinderIntoLiveImage(rect: RectF?, width: Float, height: Float, rotatedDegrees: Int): RectF
408 if (rotatedDegrees == 0 || rotatedDegrees == 180)
410 top = rect.top * height
411 bottom = rect.bottom * height
412 left = rect.left * width
413 right = rect.right * width
417 left = rect.top * height
418 right = rect.bottom * height
419 top = rect.left * width
420 bottom = rect.right * width
428 //Log.v(TAG, " [$left,$top]-[$right,$bottom]")
429 return RectF(left, top, right, bottom)
432 private fun convertRectFromImageArea(rect: RectF): RectF
434 val imageTopLeft = PointF(rect.left, rect.top)
435 val imageBottomRight = PointF(rect.right, rect.bottom)
436 val viewTopLeft: PointF = convertPointFromImageArea(imageTopLeft)
437 val viewBottomRight: PointF = convertPointFromImageArea(imageBottomRight)
438 return RectF(viewTopLeft.x, viewTopLeft.y, viewBottomRight.x, viewBottomRight.y)
441 private fun convertPointFromImageArea(point: PointF): PointF
443 var viewPointX = point.x
444 var viewPointY = point.y
445 val imageSizeWidth: Float
446 val imageSizeHeight: Float
447 if (imageRotationDegrees == 0 || imageRotationDegrees == 180)
449 imageSizeWidth = (imageBitmap.width).toFloat()
450 imageSizeHeight = (imageBitmap.height).toFloat()
454 imageSizeWidth = imageBitmap.height.toFloat()
455 imageSizeHeight = imageBitmap.width.toFloat()
457 val viewSizeWidth: Float = this.width.toFloat()
458 val viewSizeHeight: Float = this.height.toFloat()
459 val ratioX = viewSizeWidth / imageSizeWidth
460 val ratioY = viewSizeHeight / imageSizeHeight
462 when (imageScaleType)
464 ImageView.ScaleType.FIT_XY -> {
468 ImageView.ScaleType.FIT_CENTER, ImageView.ScaleType.CENTER_INSIDE -> {
469 scale = ratioX.coerceAtMost(ratioY)
470 //viewPointX *= scale
471 //viewPointY *= scale
472 viewPointX += (viewSizeWidth - imageSizeWidth * scale) / 2.0f
473 viewPointY += (viewSizeHeight - imageSizeHeight * scale) / 2.0f
475 ImageView.ScaleType.CENTER_CROP -> {
476 scale = ratioX.coerceAtLeast(ratioY)
479 viewPointX += (viewSizeWidth - imageSizeWidth * scale) / 2.0f
480 viewPointY += (viewSizeHeight - imageSizeHeight * scale) / 2.0f
482 ImageView.ScaleType.CENTER -> {
483 viewPointX += (viewSizeWidth / 2.0f - imageSizeWidth / 2.0f)
484 viewPointY += (viewSizeHeight / 2.0f - imageSizeHeight / 2.0f)
489 // Log.v(TAG, "(viewPointX : $viewPointX, viewPointY : $viewPointY) : $imageSizeWidth x $imageSizeHeight")
490 return PointF(viewPointX, viewPointY)
494 * ライブビュー座標系の点座標をビューファインダー座標系の点座標に変換
497 private fun convertPointOnLiveImageIntoViewfinder(point: PointF, width: Float, height: Float, rotatedDegrees: Int): PointF
499 var viewFinderPointX = 0.5f
500 var viewFinderPointY = 0.5f
503 if (rotatedDegrees == 0 || rotatedDegrees == 180) {
504 viewFinderPointX = point.x / width
505 viewFinderPointY = point.y / height
507 viewFinderPointX = point.y / width
508 viewFinderPointY = point.x / height
511 catch (e: java.lang.Exception)
515 return PointF(viewFinderPointX, viewFinderPointY)
519 * Converts a point on view area to a point on image area.
521 * @param point A point on view area. (e.g. a touch panel view)
522 * @return A point on image area. (e.g. a live preview image)
524 private fun convertPointFromViewArea(point: PointF): PointF
526 var imagePointX = point.x
527 var imagePointY = point.y
528 val imageSizeWidth: Float
529 val imageSizeHeight: Float
530 if (imageRotationDegrees == 0 || imageRotationDegrees == 180) {
531 imageSizeWidth = imageBitmap.width.toFloat()
532 imageSizeHeight = imageBitmap.height.toFloat()
534 imageSizeWidth = imageBitmap.height.toFloat()
535 imageSizeHeight = imageBitmap.width.toFloat()
537 val viewSizeWidth = this.width.toFloat()
538 val viewSizeHeight = this.height.toFloat()
539 val ratioX = viewSizeWidth / imageSizeWidth
540 val ratioY = viewSizeHeight / imageSizeHeight
541 val scale: Float // = 1.0f;
542 when (imageScaleType) {
543 ImageView.ScaleType.FIT_XY -> {
544 imagePointX /= ratioX
545 imagePointY /= ratioY
547 ImageView.ScaleType.FIT_CENTER, ImageView.ScaleType.CENTER_INSIDE -> {
548 scale = ratioX.coerceAtMost(ratioY)
549 imagePointX -= (viewSizeWidth - imageSizeWidth * scale) / 2.0f
550 imagePointY -= (viewSizeHeight - imageSizeHeight * scale) / 2.0f
554 ImageView.ScaleType.CENTER_CROP -> {
555 scale = ratioX.coerceAtLeast(ratioY)
556 imagePointX -= (viewSizeWidth - imageSizeWidth * scale) / 2.0f
557 imagePointY -= (viewSizeHeight - imageSizeHeight * scale) / 2.0f
561 ImageView.ScaleType.CENTER -> {
562 imagePointX -= (viewSizeWidth - imageSizeWidth) / 2.0f
563 imagePointY -= (viewSizeHeight - imageSizeHeight) / 2.0f
568 return PointF(imagePointX, imagePointY)