Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
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
310
BluetoothDevice.getName()に裏切られた話
lvla
0
440
Jetpack Compose drag gesture and pinch gesture
lvla
1
4.4k
Jetpack Compose Layout API
lvla
1
730
BLEを使ったアプリを継続的に開発するために
lvla
0
1.1k
RecyclerView.ItemAnimator
lvla
1
390
RecycledViewPool
lvla
1
310
CameraX
lvla
2
2.6k
Other Decks in Programming
See All in Programming
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
130
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
160
AI時代のコードレビューは人に向けるな、仕組みに向けろ
texmeijin
5
3.5k
Agents on Rails - Rails at Scale 2026
irinanazarova
0
340
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
20
21k
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
8
5.5k
setup-vp GitLab対応の裏側
naokihaba
0
150
最近”作らせてる” ローカルAIアプリの話
akit37
0
100
すこし踏み込む CancellationToken
htkym
2
1.6k
Agentic Software Factoryに、すごく賢いIF文を。 / super smart IF statement into the Agentic Software Factory.
rkaga
8
1.9k
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
910
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
34
15k
Featured
See All Featured
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
530
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
440
Heart Work Chapter 1 - Part 1
lfama
PRO
10
36k
So, you think you're a good person
axbom
PRO
2
2.2k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
470
Designing for Performance
lara
611
70k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
340
A better future with KSS
kneath
240
18k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3k
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