├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── _layouts └── default.html ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── example │ │ └── selfiesegmentation │ │ ├── MainActivity.kt │ │ ├── ui │ │ ├── MainFragment.kt │ │ └── MainViewModel.kt │ │ └── util │ │ ├── BitmapUtils.kt │ │ └── SegmentHelper.kt │ └── res │ ├── drawable │ ├── beach.jpg │ ├── ic_launcher_foreground.xml │ └── woman.jpg │ ├── layout │ ├── main_activity.xml │ └── main_fragment.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── doc ├── custom_bg.png ├── default.png └── mask.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | .idea 41 | *.iml 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | 54 | # Keystore files 55 | # Uncomment the following lines if you do not want to check your keystore files in. 56 | #*.jks 57 | #*.keystore 58 | 59 | # External native build folder generated in Android Studio 2.2 and later 60 | .externalNativeBuild 61 | .cxx/ 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | fastlane/readme.md 77 | 78 | # Version control 79 | vcs.xml 80 | 81 | # lint 82 | lint/intermediates/ 83 | lint/generated/ 84 | lint/outputs/ 85 | lint/tmp/ 86 | # lint/reports/ 87 | 88 | # Android Profiling 89 | *.hprof -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Gasser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SelfieSegmentation 2 | This project provides an example implementation of the ML Kit Selfie Segmentation API in an Android app. 3 | 4 | ## Installation 5 | Just clone the repo, open the project in Android Studio and run the project. 6 | For better performance run it on a physical device instead of AVD. 7 | 8 | ## Features 9 | The user can choose a front and a background image from his library. Then the image is processed and three modes can be selected. 10 | 11 | ### Default mode 12 | Displays the chosen front image. 13 | 14 | 15 | 16 | ### Mask mode 17 | Shows the front image and highlights the detected background of the segmentation mask as a green overlay. 18 | 19 | 20 | 21 | ### Custom background mode 22 | Shows the front image and uses the detected segmentation mask to overlay the background image. 23 | 24 | 25 | 26 | ## Limitations 27 | Since processing of a large image can take quite some time a selected image is resized to the current screen width. 28 | Furthermore, a background image is stretched to the same size as the current front image to simplify the overlay creation. 29 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if site.google_analytics %} 6 | 7 | 13 | {% endif %} 14 | 15 | 16 | {% seo %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Skip to the content. 26 | 27 | 38 | 39 |
40 | {{ content }} 41 | 42 | 47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 32 8 | 9 | defaultConfig { 10 | applicationId "com.example.selfieseg" 11 | minSdkVersion 19 12 | targetSdkVersion 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | vectorDrawables.useSupportLibrary = true 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | 35 | buildFeatures { 36 | viewBinding true 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation 'androidx.core:core-ktx:1.7.0' 42 | implementation 'androidx.appcompat:appcompat:1.4.1' 43 | implementation 'com.google.android.material:material:1.5.0' 44 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 45 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 46 | 47 | implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta1' 48 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neat-web/SelfieSegmentation/679926ed5664264a1bdb742f1b60e5d18134bccb/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/example/selfiesegmentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.selfiesegmentation 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.view.View 6 | import com.example.selfiesegmentation.ui.MainFragment 7 | 8 | /** 9 | * Simple wrapper for the fragment 10 | */ 11 | class MainActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN 17 | 18 | setContentView(R.layout.main_activity) 19 | if (savedInstanceState == null) { 20 | supportFragmentManager.beginTransaction() 21 | .replace(R.id.container, MainFragment()) 22 | .commitNow() 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/selfiesegmentation/ui/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.selfiesegmentation.ui 2 | 3 | import android.content.res.Resources 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ImageView 10 | import android.widget.RadioGroup 11 | import androidx.activity.result.contract.ActivityResultContracts 12 | import androidx.fragment.app.Fragment 13 | import androidx.lifecycle.ViewModelProvider 14 | import com.example.selfiesegmentation.R 15 | import com.example.selfiesegmentation.databinding.MainFragmentBinding 16 | import com.example.selfiesegmentation.util.getBitmapFromRes 17 | import com.example.selfiesegmentation.util.getBitmapFromUri 18 | import java.io.IOException 19 | 20 | /** 21 | * MainFragment of the app 22 | */ 23 | class MainFragment : Fragment() { 24 | private var _binding: MainFragmentBinding? = null 25 | private val binding get() = _binding!! 26 | 27 | private lateinit var homeViewModel: MainViewModel 28 | private lateinit var image: ImageView 29 | private lateinit var selectMode: RadioGroup 30 | private val displayWidth = Resources.getSystem().displayMetrics.widthPixels 31 | 32 | override fun onCreateView( 33 | inflater: LayoutInflater, 34 | container: ViewGroup?, 35 | savedInstanceState: Bundle? 36 | ): View { 37 | homeViewModel = ViewModelProvider(this)[MainViewModel::class.java] 38 | _binding = MainFragmentBinding.inflate(inflater, container, false) 39 | 40 | // If we started the app for the first time, we load default images into the ViewModel 41 | if (!homeViewModel.isInitialized) { 42 | initializeDefaultImages() 43 | homeViewModel.isInitialized = true 44 | } 45 | 46 | // Observe the ViewModel's image 47 | image = binding.image 48 | homeViewModel.currentImage.observe(viewLifecycleOwner) { 49 | image.setImageBitmap(it) 50 | } 51 | 52 | // OnClickListeners for choosing an image 53 | binding.selectFrontButton.setOnClickListener { 54 | homeViewModel.choseFront = true 55 | chooseImage.launch("image/*") 56 | } 57 | binding.selectBgButton.setOnClickListener { 58 | homeViewModel.choseFront = false 59 | chooseImage.launch("image/*") 60 | } 61 | 62 | // Tell the ViewModel when a new mode is chosen 63 | selectMode = binding.selectMode 64 | binding.selectMode.setOnCheckedChangeListener { _, isChecked -> 65 | homeViewModel.modeSelected(isChecked) 66 | } 67 | 68 | // Observe the ViewModel's selected mode 69 | homeViewModel.selectedMode.observe(viewLifecycleOwner) { 70 | selectMode.check(it) 71 | } 72 | 73 | return binding.root 74 | } 75 | 76 | override fun onDestroyView() { 77 | super.onDestroyView() 78 | _binding = null 79 | } 80 | 81 | /** 82 | * Load a bitmap using the URI and inform the ViewModel about it 83 | */ 84 | private fun imageChosen(imageUri: Uri) { 85 | try { 86 | val imageBitmap = getBitmapFromUri( 87 | requireActivity().contentResolver, 88 | imageUri, 89 | displayWidth 90 | ) 91 | homeViewModel.imageChosen(imageBitmap) 92 | } catch (e: IOException) { 93 | println("Error occurred while trying to load the selected image: $e") 94 | } 95 | } 96 | 97 | /** 98 | * Start intent for requesting an image from the library 99 | */ 100 | private val chooseImage = 101 | registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> 102 | if (uri != null) { 103 | imageChosen(uri) 104 | } else { 105 | println("Image could not be chosen") 106 | } 107 | } 108 | 109 | /** 110 | * Loads two sample images as if they were chosen from the user 111 | */ 112 | private fun initializeDefaultImages() { 113 | val initialFront = getBitmapFromRes(resources, R.drawable.woman, displayWidth) 114 | homeViewModel.imageChosen(initialFront) 115 | 116 | homeViewModel.choseFront = false 117 | val initialBg = getBitmapFromRes(resources, R.drawable.beach, displayWidth) 118 | homeViewModel.imageChosen(initialBg) 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/selfiesegmentation/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.selfiesegmentation.ui 2 | 3 | import android.graphics.Bitmap 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import com.example.selfiesegmentation.R 8 | import com.example.selfiesegmentation.util.ProcessedListener 9 | import com.example.selfiesegmentation.util.SegmentHelper 10 | import com.example.selfiesegmentation.util.resizeBitmap 11 | 12 | /** 13 | * ViewModel of the fragment 14 | */ 15 | class MainViewModel : ViewModel(), ProcessedListener { 16 | private val _currentImage = MutableLiveData() 17 | val currentImage: LiveData = _currentImage 18 | 19 | private val _selectedMode = MutableLiveData() 20 | val selectedMode: LiveData = _selectedMode 21 | 22 | var choseFront = true 23 | var isInitialized = false 24 | 25 | private var _normalImage: Bitmap 26 | private var _maskImage: Bitmap 27 | private var _maskBgImage: Bitmap 28 | private var _bgImage: Bitmap 29 | 30 | private var _segmentHelper: SegmentHelper = SegmentHelper(this) 31 | 32 | init { 33 | // Initialize dummy bitmap so we don't need to make the variables nullable 34 | _normalImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) 35 | _bgImage = _normalImage 36 | _maskImage = _normalImage 37 | _maskBgImage = _normalImage 38 | 39 | // At startup normal mode is selected 40 | _selectedMode.apply { value = R.id.show_normal } 41 | } 42 | 43 | /** 44 | * Processes a chosen image 45 | */ 46 | fun imageChosen(bmp: Bitmap) { 47 | if (choseFront) { 48 | _normalImage = bmp 49 | _bgImage = resizeBitmap(_bgImage, _normalImage.width, _normalImage.height) 50 | _segmentHelper.processImage(_normalImage) 51 | } else { 52 | _bgImage = resizeBitmap(bmp, _normalImage.width, _normalImage.height) 53 | _maskBgImage = _segmentHelper.generateMaskBgImage(_normalImage, _bgImage) 54 | setCurrentImage() 55 | } 56 | } 57 | 58 | /** 59 | * Updates the observed bitmap depending on the current mode 60 | */ 61 | private fun setCurrentImage() { 62 | _currentImage.apply { 63 | value = when (selectedMode.value) { 64 | R.id.show_normal -> { 65 | println("normal selected") 66 | _normalImage 67 | } 68 | R.id.show_mask -> { 69 | println("mask selected") 70 | _maskImage 71 | } 72 | R.id.show_custombg -> { 73 | println("custombg selected") 74 | _maskBgImage 75 | } 76 | else -> { 77 | println("id ${selectedMode.value} is not a valid mode") 78 | _normalImage 79 | } 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * Sets the selected mode 86 | */ 87 | fun modeSelected(id: Int) { 88 | _selectedMode.apply { value = id } 89 | setCurrentImage() 90 | } 91 | 92 | /** 93 | * Updates the images once an image was processed 94 | */ 95 | override fun imageProcessed() { 96 | _maskImage = _segmentHelper.generateMaskImage(_normalImage) 97 | _maskBgImage = _segmentHelper.generateMaskBgImage(_normalImage, _bgImage) 98 | setCurrentImage() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/selfiesegmentation/util/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.example.selfiesegmentation.util 2 | 3 | import android.content.ContentResolver 4 | import android.content.res.Resources 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.graphics.Canvas 8 | import android.graphics.Matrix 9 | import android.net.Uri 10 | import android.provider.MediaStore 11 | import kotlin.math.roundToInt 12 | 13 | /** 14 | * Merges two bitmaps by drawing them on a canvas 15 | * 16 | * Source: https://stackoverflow.com/a/40546729 17 | */ 18 | fun mergeBitmaps(bmp1: Bitmap, bmp2: Bitmap): Bitmap { 19 | val merged = Bitmap.createBitmap(bmp1.width, bmp1.height, bmp1.config) 20 | val canvas = Canvas(merged) 21 | canvas.drawBitmap(bmp1, Matrix(), null) 22 | canvas.drawBitmap(bmp2, Matrix(), null) 23 | return merged 24 | } 25 | 26 | /** 27 | * Resizes a bitmap to the given height and width 28 | */ 29 | fun resizeBitmap(bmp: Bitmap, width: Int, height: Int): Bitmap { 30 | return Bitmap.createScaledBitmap(bmp, width, height, false) 31 | } 32 | 33 | /** 34 | * Resizes a bitmap to the given width and calculates height with respect to the aspect ratio 35 | * 36 | * Source: https://stackoverflow.com/a/28921075 37 | */ 38 | fun resizeBitmapWithAspect(bmp: Bitmap, width: Int): Bitmap { 39 | val aspectRatio: Float = bmp.width / bmp.height.toFloat() 40 | val height = (width / aspectRatio).roundToInt() 41 | return resizeBitmap(bmp, width, height) 42 | } 43 | 44 | /** 45 | * Loads a bitmap from an URI 46 | */ 47 | fun getBitmapFromUri(contentResolver: ContentResolver, imageUri: Uri, width: Int): Bitmap { 48 | val bmp = MediaStore.Images.Media.getBitmap(contentResolver, imageUri) 49 | return resizeBitmapWithAspect(bmp, width) 50 | } 51 | 52 | /** 53 | * Loads a bitmap from resources 54 | */ 55 | fun getBitmapFromRes(res: Resources, id: Int, width: Int): Bitmap { 56 | val bmp = BitmapFactory.decodeResource(res, id) 57 | return resizeBitmapWithAspect(bmp, width) 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/selfiesegmentation/util/SegmentHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.selfiesegmentation.util 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import androidx.core.graphics.ColorUtils 6 | import com.google.mlkit.vision.common.InputImage 7 | import com.google.mlkit.vision.segmentation.Segmentation 8 | import com.google.mlkit.vision.segmentation.Segmenter 9 | import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions 10 | import java.nio.ByteBuffer 11 | 12 | /** 13 | * Implements functionality using the ML Kit Selfie Segmentation API 14 | */ 15 | class SegmentHelper(private val listener: ProcessedListener) { 16 | private val segmenter: Segmenter 17 | private var maskBuffer = ByteBuffer.allocate(0) 18 | private var maskWidth = 0 19 | private var maskHeight = 0 20 | 21 | init { 22 | val options = 23 | SelfieSegmenterOptions.Builder() 24 | .setDetectorMode(SelfieSegmenterOptions.SINGLE_IMAGE_MODE) 25 | .build() 26 | 27 | segmenter = Segmentation.getClient(options) 28 | } 29 | 30 | /** 31 | * Processes a bitmap and informs the listener on success 32 | */ 33 | fun processImage(image: Bitmap) { 34 | val input = InputImage.fromBitmap(image, 0) 35 | segmenter.process(input) 36 | .addOnSuccessListener { segmentationMask -> 37 | // Store all information so we can reuse it if e.g. a background images is chosen 38 | maskBuffer = segmentationMask.buffer 39 | maskWidth = segmentationMask.width 40 | maskHeight = segmentationMask.height 41 | listener.imageProcessed() 42 | } 43 | .addOnFailureListener { e -> 44 | println("Image processing failed: $e") 45 | } 46 | } 47 | 48 | /** 49 | * Highlights the detected background of the segmentation mask as a green overlay 50 | */ 51 | fun generateMaskImage(image: Bitmap): Bitmap { 52 | val maskBitmap = Bitmap.createBitmap(image.width, image.height, image.config) 53 | 54 | for (y in 0 until maskHeight) { 55 | for (x in 0 until maskWidth) { 56 | val bgConfidence = ((1.0 - maskBuffer.float) * 255).toInt() 57 | maskBitmap.setPixel(x, y, Color.argb(bgConfidence, 0, 255, 0)) 58 | } 59 | } 60 | maskBuffer.rewind() 61 | return mergeBitmaps(image, maskBitmap) 62 | } 63 | 64 | /** 65 | * Uses the detected segmentation mask to overlay the background image 66 | */ 67 | fun generateMaskBgImage(image: Bitmap, bg: Bitmap): Bitmap { 68 | val bgBitmap = Bitmap.createBitmap(image.width, image.height, image.config) 69 | 70 | for (y in 0 until maskHeight) { 71 | for (x in 0 until maskWidth) { 72 | val bgConfidence = ((1.0 - maskBuffer.float) * 255).toInt() 73 | var bgPixel = bg.getPixel(x, y) 74 | bgPixel = ColorUtils.setAlphaComponent(bgPixel, bgConfidence) 75 | bgBitmap.setPixel(x, y, bgPixel) 76 | } 77 | } 78 | maskBuffer.rewind() 79 | return mergeBitmaps(image, bgBitmap) 80 | } 81 | } 82 | 83 | /** 84 | * Interface to notify a listener about the processing status 85 | */ 86 | interface ProcessedListener { 87 | /** 88 | * Notifies the listener once the image was processed 89 | */ 90 | fun imageProcessed() 91 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neat-web/SelfieSegmentation/679926ed5664264a1bdb742f1b60e5d18134bccb/app/src/main/res/drawable/beach.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/woman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neat-web/SelfieSegmentation/679926ed5664264a1bdb742f1b60e5d18134bccb/app/src/main/res/drawable/woman.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 23 | 24 | 30 | 31 | 38 | 39 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | 66 | 67 | 72 | 73 |