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
300
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
390
RecycledViewPool
lvla
1
300
CameraX
lvla
2
2.6k
Other Decks in Programming
See All in Programming
the container ship “Apple Silicon”@WWDC26 Recap -Japan-\(region).swift
shingangan
0
110
「人を評価する AI」の設計と実装
ryoyanara
0
180
5分で問診!Composer セキュリティ健康診断
codmoninc
0
910
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
240
【QA Test Talk Vol.8】AI-DLC による Whole Team Approach の加速
pkshadeck
PRO
0
120
仕様書を書く前にハーネスを作る - Agent Native開発は「探索を速く、判定を固く」
gotalab555
4
1.6k
改善しないと、タスクが回らない。 “てんこ盛りポジション” を引き継いだ情シスの、入社3ヶ月の業務改善録
krm963
0
260
Android CLI
fornewid
0
210
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
370
PHP Application における Kubernetes 内 gRPC 通信
ganchiku
0
590
今さら聞けない .NET CLI
htkym
0
180
継続モナドとリアクティブプログラミング
yukikurage
3
700
Featured
See All Featured
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
370
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Claude Code のすすめ
schroneko
67
230k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
240
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
260
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
Ethics towards AI in product and experience design
skipperchong
2
340
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
200
How to Think Like a Performance Engineer
csswizardry
28
2.7k
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