Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Jetpack Composeで画像クロップ機能を実装する
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Moyuru Aizawa
July 14, 2023
Programming
1.3k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Jetpack Composeで画像クロップ機能を実装する
Moyuru Aizawa
July 14, 2023
More Decks by Moyuru Aizawa
See All by Moyuru Aizawa
BLUETOOTH_SCAN and iBeacon
lvla
1
150
graphicsLayer
lvla
0
290
BluetoothDevice.getName()に裏切られた話
lvla
0
410
Jetpack Compose drag gesture and pinch gesture
lvla
1
4.3k
Jetpack Compose Layout API
lvla
1
710
BLEを使ったアプリを継続的に開発するために
lvla
0
1.1k
RecyclerView.ItemAnimator
lvla
1
380
RecycledViewPool
lvla
1
290
CameraX
lvla
2
2.5k
Other Decks in Programming
See All in Programming
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
210
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
920
スマートグラスで並列バイブコーディング
hyshu
0
260
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.5k
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
130
A2UI という光を覗いてみる
satohjohn
1
150
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
360
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
170
Oxlintのカスタムルールの現況
syumai
6
1.1k
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.4k
Webフレームワークの ベンチマークについて
yusukebe
0
180
The NotImplementedError Problem in Ruby
koic
1
920
Featured
See All Featured
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
200
Testing 201, or: Great Expectations
jmmastey
46
8.2k
From π to Pie charts
rasagy
0
220
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
370
How Software Deployment tools have changed in the past 20 years
geshan
0
34k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
330
The browser strikes back
jonoalderson
0
1.3k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
GitHub's CSS Performance
jonrohan
1033
470k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1.1k
The SEO identity crisis: Don't let AI make you average
varn
0
500
Transcript
Jetpack ComposeͰ ը૾ΫϩοϓػೳΛ࣮͢Δ @MoyuruAizawa
Moyuru Aizawa Software Engineer of Catlog, RABO. Previously at Azit,
CyberAgent, and Eureka. Love Metal, Hardcore and EDM. MoyuruAizawa
github.com/MoyuruAizawa/Cropify
‣ Jetpack ComposeͰ͑ΔImage Cropper͕ཉ͍͠ ‣ ArthurHub/Android-Image-Cropper ‣ ViewͷੈքͰ͓ੈʹͳͬͯͨ ‣ Compose
+ AndroidViewͰ͑ͳ͍ (ಉ྅͕ݕূͨ݁͠Ռͦ͏ݴͬͯͨΑ) ‣ SmartToolFactory/Compose-Cropper ‣ Android-Image-Cropperͱૢ࡞ײ͕ҟͳΔ ‣ BitmapͷαϯϓϦϯάಡΈࠐΈʹରԠ͍ͯ͠ͳ͍ Motivation
ࣗ࡞͢Δ͔…!!
1. αϯϓϦϯάͨ͠BitmapΛϩʔυ͢Δ 2. CanvasʹBitmapΛඳը͢Δ 3. Canvasͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ 4. Ϣʔβʔૢ࡞ʹैͬͯΫϩοϓϑϨʔϜΛҠಈ/֦ॖ͢Δ 5. ϑϨʔϜͷ࠲ඪͱαΠζΛऔͬͯBitmap͔Βը૾Λൈ͖औΔ
࣮ͷ֓ཁ
αϯϓϦϯάͨ͠BitmapΛϩʔ υ͢Δ
‣ Image/Canvas/ImageViewΑΓང͔ʹେ͖͍ը૾Λ ͦͷ··දࣔ͢ΔͷϝϞϦͷແବ ‣ αϯϓϦϯάͯ͠ը૾ΛBitmapʹ͓ͤ͜ޮత ‣ 🔍 AndroidDevelopers ”Loading Large
Bitmaps Efficiently” αϯϓϦϯάͨ͠BitmapΛϩʔυ͢Δ
CanvasʹBitmapΛදࣔ͢Δ
Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize
= size.toInt(), dstOffset = offset.toInt(), ) } CanvasʹBitmapΛදࣔ͢Δ
Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize
= size.toInt(), dstOffset = offset.toInt(), ) } എܠΛCanvas͍ͬͺʹඳը
Canvas(modifier = modifier) { drawRect(option.backgroundColor) drawImage( image = bitmap, dstSize
= size.toInt(), dstOffset = offset.toInt(), ) } CanvasʹBitmapΛFitCenterʹͳΔΑ͏ʹඳը
Canvasͷ্ʹ ΫϩοϓϑϨʔϜΛඳը͢Δ
‣ ΫϩοϓϑϨʔϜΛඳը͢Δ ‣ ΫϩοϓϑϨʔϜͷ֎ଆ҉͘͢Δ ‣ PorterDuff Canvasͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ
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ͷ্ʹΫϩοϓϑϨʔϜΛඳը͢Δ
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ͰϚεΫ͢Δ
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”
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Λઃఆ͓ͯ͘͠
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) } } ϑϨʔϜΛඳը͢Δ
Ϣʔβʔૢ࡞ʹैͬͯ ΫϩοϓϑϨʔϜΛҠಈ/֦ॖ͢Δ
‣ 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)) }
modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … },
onDragEnd = { … }, onDrag = { … } ) } ΫϩοϓϑϨʔϜͷҠಈ/֦ॖ
modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … },
onDragEnd = { … }, onDrag = { … } ) } ϑϨʔϜͷͲ͜Λ৮ͬͨͷ͔Λผ͢Δ
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Λܭࢉ
modifier .pointerInput(bitmap, option.frameAspectRatio) { detectDragGestures( onDragStart = { … },
onDragEnd = { … }, onDrag = { … } ) } ϑϨʔϜͷ֦ॖ/ҠಈΛߦ͏
onDrag = { change, dragAmount -> touchRegion?.let { when (it)
{ is TouchRegion.Vertex -> state.scaleFrameRect(…) TouchRegion.Inside -> state.translateFrameRect(…) } change.consume() } } TouchRegionͱdragAmountΛΈͯϑϨʔϜΛ֦ॖ/Ҡಈ
‣ ϑϨʔϜը૾ͷ֎ʹग़͍͚ͯͳ͍ ‣ ϑϨʔϜ֦ॖͰ࠲ඪΛҠಈ͢Δ߹ ྡͷΛ͑ͯͳΒͳ͍ ‣ ΞεϖΫτൺݻఆͷϑϨʔϜ֦ॖͰ࠲ඪΛҠಈ͢Δ߹ ͍͍ײ͡ʹΞεϖΫτൺΛҡ࣋ͯ͠࠲ඪΛಈ͔͢ ϑϨʔϜͷ֦ॖͪΐͬͱͩΔ͍
Bitmap͔Βը૾ΛΓൈ͘
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͔Βը૾ΛΓൈ͘
؆୯ʹImage Cropper࡞Εͨ🥰
Thank you