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
300
BluetoothDevice.getName()に裏切られた話
lvla
0
410
Jetpack Compose drag gesture and pinch gesture
lvla
1
4.3k
Jetpack Compose Layout API
lvla
1
720
BLEを使ったアプリを継続的に開発するために
lvla
0
1.1k
RecyclerView.ItemAnimator
lvla
1
390
RecycledViewPool
lvla
1
300
CameraX
lvla
2
2.6k
Other Decks in Programming
See All in Programming
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
170
関東Kaggler会_NVIDIA_Nemotron_コンペ_振り返り
rick_ds
0
740
仕様駆動開発の消費期限
watany
20
9.1k
ソフトウェアラスタライザ
fadis
1
730
高専、大学編入、そして未踏へ〜プロダクト開発とキャリアの歩み - Technical College, University Transfer, and On to “Mitou” / My Journey in Product Development and Career
pkmiya
0
110
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
1
400
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
3.9k
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
240
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
400
業務時間外もAIに働いてもらう話
colorful12
2
9k
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.3k
自動化したのに回らない テスト運用の壁―AI時代の品質責任と生産性
mfunaki
0
550
Featured
See All Featured
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
500
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Thoughts on Productivity
jonyablonski
76
5.3k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
60k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
45k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
430
Mobile First: as difficult as doing things right
swwweet
225
10k
Facilitating Awesome Meetings
lara
57
7.1k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
350
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
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