-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,786 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.github.ferhatwi.crop.callback; | ||
|
||
public interface CropBoundsChangeListener { | ||
|
||
void onCropAspectRatioChanged(float cropRatio); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package io.github.ferhatwi.crop.callback; | ||
|
||
import android.graphics.RectF; | ||
|
||
public interface OverlayViewChangeListener { | ||
|
||
void onCropRectUpdated(RectF cropRect); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.github.ferhatwi.crop.model | ||
|
||
import android.graphics.Paint | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.toArgb | ||
|
||
data class Frame( | ||
var visible: Boolean = true, | ||
var edgePaint: Paint = Paint().apply { | ||
style = Paint.Style.STROKE | ||
color = Color.White.copy(alpha = 0.5f).toArgb() | ||
strokeWidth = 1f | ||
}, | ||
var cornerPaint: Paint = Paint().apply { | ||
style = Paint.Style.STROKE | ||
color = Color.White.copy(alpha = 0.5f).toArgb() | ||
strokeWidth = 3f | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.github.ferhatwi.crop.model | ||
|
||
import android.graphics.Paint | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.toArgb | ||
|
||
data class Grid( | ||
var visible: Boolean = true, | ||
var row: Int = 3, | ||
var column: Int = 3, | ||
var paint: Paint = Paint().apply { | ||
style = Paint.Style.STROKE | ||
color = Color.White.copy(alpha = 0.5f).toArgb() | ||
strokeWidth = 1f | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package io.github.ferhatwi.crop.model | ||
|
||
import android.graphics.RectF | ||
|
||
data class ImageState( | ||
val cropRect: RectF, | ||
val imageRect: RectF, | ||
var scale: Float, | ||
val angle: Float | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
import android.content.res.Resources | ||
import android.graphics.Canvas | ||
import android.util.DisplayMetrics | ||
import android.util.Log | ||
import kotlin.math.min | ||
import kotlin.math.pow | ||
import kotlin.math.sqrt | ||
|
||
private const val TAG = "BitmapLoadUtils" | ||
|
||
object BitmapLoadUtils { | ||
|
||
fun calculateMaxBitmapSize(): Int { | ||
val displayMetrics: DisplayMetrics by lazy { Resources.getSystem().displayMetrics } | ||
val size = displayMetrics.run { widthPixels to heightPixels } | ||
|
||
val width = size.first | ||
val height = size.second | ||
|
||
// Twice the device screen diagonal as default | ||
var maxBitmapSize = | ||
sqrt(width.toDouble().pow(2) + height.toDouble().pow(2)).toInt() | ||
|
||
// Check for max texture size via Canvas | ||
val canvas = Canvas() | ||
val maxCanvasSize = min(canvas.maximumBitmapWidth, canvas.maximumBitmapHeight) | ||
if (maxCanvasSize > 0) { | ||
maxBitmapSize = min(maxBitmapSize, maxCanvasSize) | ||
} | ||
|
||
// Check for max texture size via GL | ||
val maxTextureSize = EglUtils.maxTextureSize | ||
if (maxTextureSize > 0) { | ||
maxBitmapSize = min(maxBitmapSize, maxTextureSize) | ||
} | ||
Log.d(TAG, "maxBitmapSize: $maxBitmapSize") | ||
return maxBitmapSize | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
object CubicEasing { | ||
fun easeOut(time: Float, start: Float, end: Float, duration: Float): Float { | ||
var timeX = time | ||
return end * (((timeX / duration - 1.0f).also { | ||
timeX = it | ||
}) * timeX * timeX + 1.0f) + start | ||
} | ||
|
||
fun easeIn(time: Float, start: Float, end: Float, duration: Float): Float { | ||
var timeX = time | ||
return end * duration.let { timeX /= it; timeX } * timeX * timeX + start | ||
} | ||
|
||
fun easeInOut(time: Float, start: Float, end: Float, duration: Float): Float { | ||
var timeX = time | ||
return if ((duration / 2.0f).let { timeX /= it; timeX } < 1.0f) end / 2.0f * timeX * timeX * timeX + start else end / 2.0f * (2.0f.let { timeX -= it; timeX } * timeX * timeX + 2.0f) + start | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
import android.view.View | ||
import kotlin.math.roundToInt | ||
|
||
internal fun View.toDp(int: Int) : Int = toDp(int.toFloat()).roundToInt() | ||
|
||
fun View.toDp(float: Float) : Float = resources.displayMetrics.density*float |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
import android.opengl.EGL14 | ||
import android.opengl.EGLConfig | ||
import android.opengl.GLES20 | ||
import android.util.Log | ||
|
||
private const val TAG = "EglUtils" | ||
|
||
object EglUtils { | ||
val maxTextureSize: Int | ||
get() = kotlin.runCatching { | ||
val dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY) | ||
val vers = IntArray(2) | ||
EGL14.eglInitialize(dpy, vers, 0, vers, 1) | ||
val configAttr = intArrayOf( | ||
EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER, | ||
EGL14.EGL_LEVEL, 0, | ||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, | ||
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, | ||
EGL14.EGL_NONE | ||
) | ||
val configs = arrayOfNulls<EGLConfig>(1) | ||
val numConfig = IntArray(1) | ||
EGL14.eglChooseConfig( | ||
dpy, configAttr, 0, | ||
configs, 0, 1, numConfig, 0 | ||
) | ||
if (numConfig[0] == 0) { | ||
return 0 | ||
} | ||
val config = configs[0] | ||
val surfAttr = intArrayOf( | ||
EGL14.EGL_WIDTH, 64, | ||
EGL14.EGL_HEIGHT, 64, | ||
EGL14.EGL_NONE | ||
) | ||
val surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0) | ||
val ctxAttrib = intArrayOf( | ||
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, | ||
EGL14.EGL_NONE | ||
) | ||
val ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0) | ||
EGL14.eglMakeCurrent(dpy, surf, surf, ctx) | ||
val maxSize = IntArray(1) | ||
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0) | ||
EGL14.eglMakeCurrent( | ||
dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, | ||
EGL14.EGL_NO_CONTEXT | ||
) | ||
EGL14.eglDestroySurface(dpy, surf) | ||
EGL14.eglDestroyContext(dpy, ctx) | ||
EGL14.eglTerminate(dpy) | ||
maxSize[0] | ||
}.getOrElse { | ||
Log.d(TAG, "getMaxTextureSize: ", it) | ||
0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
import android.graphics.RectF | ||
|
||
object RectUtils { | ||
/** | ||
* Gets a float array of the 2D coordinates representing a rectangles | ||
* corners. | ||
* The order of the corners in the float array is: | ||
* 0------->1 | ||
* ^ | | ||
* | | | ||
* | v | ||
* 3<-------2 | ||
* | ||
* @param r the rectangle to get the corners of | ||
* @return the float array of corners (8 floats) | ||
*/ | ||
fun getCornersFromRect(r: RectF): FloatArray { | ||
return floatArrayOf( | ||
r.left, r.top, | ||
r.right, r.top, | ||
r.right, r.bottom, | ||
r.left, r.bottom | ||
) | ||
} | ||
|
||
/** | ||
* Gets a float array of two lengths representing a rectangles width and height | ||
* The order of the corners in the input float array is: | ||
* 0------->1 | ||
* ^ | | ||
* | | | ||
* | v | ||
* 3<-------2 | ||
* | ||
* @param corners the float array of corners (8 floats) | ||
* @return the float array of width and height (2 floats) | ||
*/ | ||
fun getRectSidesFromCorners(corners: FloatArray): FloatArray { | ||
return floatArrayOf( | ||
Math.sqrt( | ||
Math.pow( | ||
(corners[0] - corners[2]).toDouble(), | ||
2.0 | ||
) + Math.pow((corners[1] - corners[3]).toDouble(), 2.0) | ||
).toFloat(), Math.sqrt( | ||
Math.pow( | ||
(corners[2] - corners[4]).toDouble(), | ||
2.0 | ||
) + Math.pow((corners[3] - corners[5]).toDouble(), 2.0) | ||
).toFloat() | ||
) | ||
} | ||
|
||
fun getCenterFromRect(r: RectF): FloatArray { | ||
return floatArrayOf(r.centerX(), r.centerY()) | ||
} | ||
|
||
/** | ||
* Takes an array of 2D coordinates representing corners and returns the | ||
* smallest rectangle containing those coordinates. | ||
* | ||
* @param array array of 2D coordinates | ||
* @return smallest rectangle containing coordinates | ||
*/ | ||
fun trapToRect(array: FloatArray): RectF { | ||
val r = RectF( | ||
Float.POSITIVE_INFINITY, | ||
Float.POSITIVE_INFINITY, | ||
Float.NEGATIVE_INFINITY, | ||
Float.NEGATIVE_INFINITY | ||
) | ||
var i = 1 | ||
while (i < array.size) { | ||
val x = Math.round(array[i - 1] * 10) / 10f | ||
val y = Math.round(array[i] * 10) / 10f | ||
r.left = Math.min(x, r.left) | ||
r.top = Math.min(y, r.top) | ||
r.right = Math.max(x, r.right) | ||
r.bottom = Math.max(y, r.bottom) | ||
i += 2 | ||
} | ||
r.sort() | ||
return r | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package io.github.ferhatwi.crop.util | ||
|
||
import android.view.MotionEvent | ||
|
||
class RotationGestureDetector(private val mListener: OnRotationGestureListener?) { | ||
private var fX = 0f | ||
private var fY = 0f | ||
private var sX = 0f | ||
private var sY = 0f | ||
private var mPointerIndex1: Int | ||
private var mPointerIndex2: Int | ||
var angle = 0f | ||
private set | ||
private var mIsFirstTouch = false | ||
fun onTouchEvent(event: MotionEvent): Boolean { | ||
when (event.actionMasked) { | ||
MotionEvent.ACTION_DOWN -> { | ||
sX = event.x | ||
sY = event.y | ||
mPointerIndex1 = event.findPointerIndex(event.getPointerId(0)) | ||
angle = 0f | ||
mIsFirstTouch = true | ||
} | ||
MotionEvent.ACTION_POINTER_DOWN -> { | ||
fX = event.x | ||
fY = event.y | ||
mPointerIndex2 = event.findPointerIndex(event.getPointerId(event.actionIndex)) | ||
angle = 0f | ||
mIsFirstTouch = true | ||
} | ||
MotionEvent.ACTION_MOVE -> if (mPointerIndex1 != INVALID_POINTER_INDEX && mPointerIndex2 != INVALID_POINTER_INDEX && event.pointerCount > mPointerIndex2) { | ||
val nfX: Float | ||
val nfY: Float | ||
val nsX: Float | ||
val nsY: Float | ||
nsX = event.getX(mPointerIndex1) | ||
nsY = event.getY(mPointerIndex1) | ||
nfX = event.getX(mPointerIndex2) | ||
nfY = event.getY(mPointerIndex2) | ||
if (mIsFirstTouch) { | ||
angle = 0f | ||
mIsFirstTouch = false | ||
} else { | ||
calculateAngleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY) | ||
} | ||
mListener?.onRotation(this) | ||
fX = nfX | ||
fY = nfY | ||
sX = nsX | ||
sY = nsY | ||
} | ||
MotionEvent.ACTION_UP -> mPointerIndex1 = INVALID_POINTER_INDEX | ||
MotionEvent.ACTION_POINTER_UP -> mPointerIndex2 = INVALID_POINTER_INDEX | ||
} | ||
return true | ||
} | ||
|
||
private fun calculateAngleBetweenLines( | ||
fx1: Float, fy1: Float, fx2: Float, fy2: Float, | ||
sx1: Float, sy1: Float, sx2: Float, sy2: Float | ||
): Float { | ||
return calculateAngleDelta( | ||
Math.toDegrees( | ||
Math.atan2((fy1 - fy2).toDouble(), (fx1 - fx2).toDouble()).toFloat().toDouble() | ||
).toFloat(), Math.toDegrees( | ||
Math.atan2((sy1 - sy2).toDouble(), (sx1 - sx2).toDouble()).toFloat().toDouble() | ||
).toFloat() | ||
) | ||
} | ||
|
||
private fun calculateAngleDelta(angleFrom: Float, angleTo: Float): Float { | ||
angle = angleTo % 360.0f - angleFrom % 360.0f | ||
if (angle < -180.0f) { | ||
angle += 360.0f | ||
} else if (angle > 180.0f) { | ||
angle -= 360.0f | ||
} | ||
return angle | ||
} | ||
|
||
open class SimpleOnRotationGestureListener : OnRotationGestureListener { | ||
override fun onRotation(rotationDetector: RotationGestureDetector): Boolean { | ||
return false | ||
} | ||
} | ||
|
||
interface OnRotationGestureListener { | ||
fun onRotation(rotationDetector: RotationGestureDetector): Boolean | ||
} | ||
|
||
companion object { | ||
private const val INVALID_POINTER_INDEX = -1 | ||
} | ||
|
||
init { | ||
mPointerIndex1 = INVALID_POINTER_INDEX | ||
mPointerIndex2 = INVALID_POINTER_INDEX | ||
} | ||
} |
Oops, something went wrong.