Slide 1

Slide 1 text

Jetpack ComposeͰ ը૾ΫϩοϓػೳΛ࣮૷͢Δ @MoyuruAizawa

Slide 2

Slide 2 text

Moyuru Aizawa Software Engineer of Catlog, RABO. Previously at Azit, CyberAgent, and Eureka. Love Metal, Hardcore and EDM. MoyuruAizawa

Slide 3

Slide 3 text

github.com/MoyuruAizawa/Cropify

Slide 4

Slide 4 text

‣ Jetpack ComposeͰ࢖͑ΔImage Cropper͕ཉ͍͠ ‣ ArthurHub/Android-Image-Cropper ‣ ViewͷੈքͰ͓ੈ࿩ʹͳͬͯͨ ‣ Compose + AndroidViewͰ࢖͑ͳ͍ (ಉ྅͕ݕূͨ݁͠Ռͦ͏ݴͬͯͨΑ) ‣ SmartToolFactory/Compose-Cropper ‣ Android-Image-Cropperͱૢ࡞ײ͕ҟͳΔ ‣ BitmapͷαϯϓϦϯάಡΈࠐΈʹରԠ͍ͯ͠ͳ͍ Motivation

Slide 5

Slide 5 text

ࣗ࡞͢Δ͔…!!

Slide 6

Slide 6 text

1. αϯϓϦϯάͨ͠BitmapΛϩʔυ͢Δ 2. CanvasʹBitmapΛඳը͢Δ 3. Canvasͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ 4. Ϣʔβʔૢ࡞ʹैͬͯΫϩοϓϑϨʔϜΛҠಈ/֦ॖ͢Δ 5. ϑϨʔϜͷ࠲ඪͱαΠζΛऔͬͯBitmap͔Βը૾Λൈ͖औΔ ࣮૷ͷ֓ཁ

Slide 7

Slide 7 text

αϯϓϦϯάͨ͠BitmapΛϩʔ υ͢Δ

Slide 8

Slide 8 text

‣ Image/Canvas/ImageViewΑΓ΋ང͔ʹେ͖͍ը૾Λ ͦͷ··දࣔ͢Δͷ͸ϝϞϦͷແବ ‣ αϯϓϦϯάͯ͠ը૾ΛBitmapʹ͓ͤ͜͹ޮ཰త ‣ 🔍 AndroidDevelopers ”Loading Large Bitmaps Efficiently” αϯϓϦϯάͨ͠BitmapΛϩʔυ͢Δ

Slide 9

Slide 9 text

CanvasʹBitmapΛදࣔ͢Δ

Slide 10

Slide 10 text

Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize = size.toInt(), dstOffset = offset.toInt(), ) } CanvasʹBitmapΛදࣔ͢Δ

Slide 11

Slide 11 text

Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize = size.toInt(), dstOffset = offset.toInt(), ) } എܠΛCanvas͍ͬͺʹඳը

Slide 12

Slide 12 text

Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize = size.toInt(), dstOffset = offset.toInt(), ) } CanvasʹBitmapΛFitCenterʹͳΔΑ͏ʹඳը

Slide 13

Slide 13 text

Canvasͷ্ʹ ΫϩοϓϑϨʔϜΛඳը͢Δ

Slide 14

Slide 14 text

‣ ΫϩοϓϑϨʔϜΛඳը͢Δ ‣ ΫϩοϓϑϨʔϜͷ֎ଆ͸҉͘͢Δ ‣ PorterDuff Canvasͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ

Slide 15

Slide 15 text

Canvas(modifier = modifier) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect( color = option.maskColor, alpha = option.maskAlpha, ) drawRect( color = Color.Transparent, topLeft = offset, size = size, blendMode = BlendMode.SrcOut ) restoreToCount(checkPoint) drawFrame(offset, size, option) }
 } Canvasͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ

Slide 16

Slide 16 text

Canvas(modifier = modifier) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect( color = option.maskColor, alpha = option.maskAlpha, ) drawRect( color = Color.Transparent, topLeft = offset, size = size, blendMode = BlendMode.SrcOut ) restoreToCount(checkPoint) drawFrame(offset, size, option) } } CanvasશମΛdrawRectͰϚεΫ͢Δ

Slide 17

Slide 17 text

Canvas(modifier = modifier) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect( color = option.maskColor, alpha = option.maskAlpha, ) drawRect( color = Color.Transparent, topLeft = offset, size = size, blendMode = BlendMode.SrcOut ) restoreToCount(checkPoint) drawFrame(offset, size, option) }
 } ϑϨʔϜͷ಺ଆ͚ͩSrcOutͰ͘Γൈ͘ 🔍 AndroidDevelopers “PorterDuff.Mode” 🔍 AndroidDevelopers “BlendMode”

Slide 18

Slide 18 text

Canvas(modifier = modifier) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect( color = option.maskColor, alpha = option.maskAlpha, ) drawRect( color = Color.Transparent, topLeft = offset, size = size, blendMode = BlendMode.SrcOut ) restoreToCount(checkPoint) drawFrame(offset, size, option) } } PorterDuffͰmask͚ͩΛ͘Γൈͨ͘ΊʹlayerΛઃఆ͓ͯ͘͠

Slide 19

Slide 19 text

Canvas(modifier = modifier) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect( color = option.maskColor, alpha = option.maskAlpha, ) drawRect( color = Color.Transparent, topLeft = offset, size = size, blendMode = BlendMode.SrcOut ) restoreToCount(checkPoint) drawFrame(offset, size, option) }
 } ϑϨʔϜΛඳը͢Δ

Slide 20

Slide 20 text

Ϣʔβʔૢ࡞ʹैͬͯ ΫϩοϓϑϨʔϜΛҠಈ/֦ॖ͢Δ

Slide 21

Slide 21 text

‣ Modifier#pointerInput ‣ PointerInputScope#detectDragGestures ‣ ͜ͷ2ͭΛ࢖ͬͯϢʔβʔͷδΣενϟʔΛ͞͹͘ ‣ ϑϨʔϜͷ࠲ඪ/αΠζ Ŋ ը૾ͷ࠲ඪ/αΠζΛߟྀ͢Δඞཁ͕͋ΔŇ Stateͱ͓ͯ࣋ͬͯ͘͠Ň ΫϩοϓϑϨʔϜͷҠಈ/֦ॖ class CropifyState { internal var frameRect by mutableStateOf(Rect(0f, 0f, 0f, 0f)) internal var imageRect by mutableStateOf(Rect(0f, 0f, 0f, 0f)) }

Slide 22

Slide 22 text

modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … }, onDragEnd = { … }, onDrag = { … } ) } ΫϩοϓϑϨʔϜͷҠಈ/֦ॖ

Slide 23

Slide 23 text

modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … }, onDragEnd = { … }, onDrag = { … } ) } ϑϨʔϜͷͲ͜Λ৮ͬͨͷ͔Λ൑ผ͢Δ

Slide 24

Slide 24 text

fun detectTouchRegion( tapPosition: Offset, frameRect: Rect, tolerance: Float ): TouchRegion? { return when { Rect(frameRect.topLeft, tolerance)
 .contains(tapPosition) -> TouchRegion.Vertex.TOP_LEFT // தུ Rect(frameRect.center, frameRect.width / 2 - tolerance)
 .contains(tapPosition) -> TouchRegion.Inside else -> null } } ϑϨʔϜͷ࠲ඪͱλον࠲ඪΛൺֱͯ͠TouchRegionΛܭࢉ

Slide 25

Slide 25 text

modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … }, onDragEnd = { … }, onDrag = { … } ) } ϑϨʔϜͷ֦ॖ/ҠಈΛߦ͏

Slide 26

Slide 26 text

onDrag = { change, dragAmount -> touchRegion?.let { when (it) { is TouchRegion.Vertex -> state.scaleFrameRect(…) TouchRegion.Inside -> state.translateFrameRect(…) } change.consume() } } TouchRegionͱdragAmountΛΈͯϑϨʔϜΛ֦ॖ/Ҡಈ

Slide 27

Slide 27 text

‣ ϑϨʔϜ͸ը૾ͷ֎ʹग़ͯ͸͍͚ͳ͍ ‣ ϑϨʔϜ֦ॖͰ௖఺࠲ඪΛҠಈ͢Δ৔߹ ྡͷ௖఺Λ௒͑ͯ͸ͳΒͳ͍ ‣ ΞεϖΫτൺݻఆͷϑϨʔϜ֦ॖͰ௖఺࠲ඪΛҠಈ͢Δ৔߹ ͍͍ײ͡ʹΞεϖΫτൺΛҡ࣋ͯ͠௖఺࠲ඪΛಈ͔͢ ϑϨʔϜͷ֦ॖ͸ͪΐͬͱͩΔ͍

Slide 28

Slide 28 text

Bitmap͔Βը૾Λ੾Γൈ͘

Slide 29

Slide 29 text

private suspend fun cropImage( bitmap: ImageBitmap, frameRect: Rect, imageRect: Rect, ): ImageBitmap { return withContext(Dispatchers.IO) { val scale = bitmap.width / imageRect.width Bitmap.createBitmap( bitmap.asAndroidBitmap(), ((frameRect.left - imageRect.left) * scale).roundToInt(), ((frameRect.top - imageRect.top) * scale).roundToInt(), (frameRect.width * scale).roundToInt(), (frameRect.height * scale).roundToInt(), ).asImageBitmap() } } Bitmap͔Βը૾Λ੾Γൈ͘

Slide 30

Slide 30 text

؆୯ʹImage Cropper࡞Εͨ🥰

Slide 31

Slide 31 text

Thank you