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
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.6k
Other Decks in Programming
See All in Programming
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
120
アルゴリズムは何を圧縮しているのか ─ Haskell から育った「圧縮代数」というメンタルモデル
naoya
16
3.6k
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
1.3k
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
910
【やさしく解説 設計編・中級 #1】一つの車に、運転手は一人 ~ある倉庫システムの事例から~
panda728
PRO
0
190
エンジニアにデザインハーネスを 〜デザインプロセスを規定するためのハーネス〜 / Design harness from an engineer's perspective
rkaga
2
1.7k
5分で問診!Composer セキュリティ健康診断
codmoninc
0
620
jsmini JavaScript Engine を作ってみた話
yosuke_furukawa
PRO
0
240
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
1
230
【やさしく解説 設計編・中級 #6】良いアーキテクチャとは ~ 一本の登り道の、行き先 ~
panda728
PRO
0
180
Hatena Engineer Seminar #37「言語モデルの活用に関する研究」
slashnephy
0
550
人間の目はかわらない、だからJPEGは30年もつ
yuzneri
10
16k
Featured
See All Featured
Paper Plane
katiecoart
PRO
2
52k
Building Adaptive Systems
keathley
44
3.1k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
390
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
190
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
180
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
470
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
740
How to Ace a Technical Interview
jacobian
281
24k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
210
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
45k
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