├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── quickbirdstudios │ │ └── yuv2mat │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── quickbirdstudios │ │ │ └── yuv2mat │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── quickbirdstudios │ └── yuv2mat │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── quickbirdstudios │ │ └── yuv2mat │ │ ├── YuvI420ImageTest.kt │ │ └── YuvN12ImageTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── quickbirdstudios │ │ │ └── yuv2mat │ │ │ ├── BufferMatrix+with+Clip.kt │ │ │ ├── BufferMatrix.kt │ │ │ ├── ByteBuffer+clip.kt │ │ │ ├── ByteBuffer+toByteArray.kt │ │ │ ├── Clip+Math.kt │ │ │ ├── Clip+adjustToYuvGrid.kt │ │ │ ├── Image+toMat.kt │ │ │ ├── Mat+contentEquals.kt │ │ │ ├── Mat+forEach.kt │ │ │ ├── Yuv+defrag.kt │ │ │ ├── Yuv.kt │ │ │ ├── YuvI420Image.kt │ │ │ ├── YuvImage.kt │ │ │ ├── YuvImageFactory.kt │ │ │ ├── YuvN12Image.kt │ │ │ ├── YuvPlanes.kt │ │ │ └── byteBuffer.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── quickbirdstudios │ └── yuv2mat │ ├── BufferMatrixWithClipTest.kt │ └── ByteBufferClipTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | ### Kotlin template 12 | # Compiled class file 13 | *.class 14 | 15 | # Log file 16 | *.log 17 | 18 | # BlueJ files 19 | *.ctxt 20 | 21 | # Mobile Tools for Java (J2ME) 22 | .mtj.tmp/ 23 | 24 | # Package Files # 25 | *.jar 26 | *.war 27 | *.nar 28 | *.ear 29 | *.zip 30 | *.tar.gz 31 | *.rar 32 | 33 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 34 | hs_err_pid* 35 | ### JetBrains template 36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 37 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 38 | 39 | # User-specific stuff 40 | .idea/**/workspace.xml 41 | .idea/**/tasks.xml 42 | .idea/**/usage.statistics.xml 43 | .idea/**/dictionaries 44 | .idea/**/shelf 45 | 46 | # Sensitive or high-churn files 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.local.xml 50 | .idea/**/sqlDataSources.xml 51 | .idea/**/dynamic.xml 52 | .idea/**/uiDesigner.xml 53 | .idea/**/dbnavigator.xml 54 | 55 | # Gradle 56 | .idea/**/gradle.xml 57 | .idea/**/libraries 58 | 59 | # Gradle and Maven with auto-import 60 | # When using Gradle or Maven with auto-import, you should exclude module files, 61 | # since they will be recreated, and may cause churn. Uncomment if using 62 | # auto-import. 63 | # .idea/modules.xml 64 | # .idea/*.iml 65 | # .idea/modules 66 | 67 | # CMake 68 | cmake-build-*/ 69 | 70 | # Mongo Explorer plugin 71 | .idea/**/mongoSettings.xml 72 | 73 | # File-based project format 74 | *.iws 75 | 76 | # IntelliJ 77 | out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Cursive Clojure plugin 86 | .idea/replstate.xml 87 | 88 | # Crashlytics plugin (for Android Studio and IntelliJ) 89 | com_crashlytics_export_strings.xml 90 | crashlytics.properties 91 | crashlytics-build.properties 92 | fabric.properties 93 | 94 | # Editor-based Rest Client 95 | .idea/httpRequests 96 | ### Gradle template 97 | /build/ 98 | 99 | # Ignore Gradle GUI config 100 | gradle-app.setting 101 | 102 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 103 | !gradle-wrapper.jar 104 | 105 | # Cache of project 106 | .gradletasknamecache 107 | 108 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 109 | # gradle/wrapper/gradle-wrapper.properties 110 | ### Java template 111 | # Compiled class file 112 | 113 | # Log file 114 | 115 | # BlueJ files 116 | 117 | # Mobile Tools for Java (J2ME) 118 | 119 | # Package Files # 120 | 121 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 122 | ### Android template 123 | # Built application files 124 | *.apk 125 | *.ap_ 126 | 127 | # Files for the ART/Dalvik VM 128 | *.dex 129 | 130 | # Java class files 131 | 132 | # Generated files 133 | bin/ 134 | gen/ 135 | 136 | # Gradle files 137 | .gradle/ 138 | build/ 139 | 140 | # Local configuration file (sdk path, etc) 141 | local.properties 142 | 143 | # Proguard folder generated by Eclipse 144 | proguard/ 145 | 146 | # Log Files 147 | 148 | # Android Studio Navigation editor temp files 149 | .navigation/ 150 | 151 | # Android Studio captures folder 152 | captures/ 153 | 154 | # IntelliJ 155 | .idea/workspace.xml 156 | .idea/tasks.xml 157 | .idea/gradle.xml 158 | .idea/assetWizardSettings.xml 159 | .idea/dictionaries 160 | .idea/libraries 161 | .idea/caches 162 | 163 | # Keystore files 164 | # Uncomment the following line if you do not want to check your keystore files in. 165 | #*.jks 166 | 167 | # External native build folder generated in Android Studio 2.2 and later 168 | 169 | # Google Services (e.g. APIs or Firebase) 170 | google-services.json 171 | 172 | # Freeline 173 | freeline.py 174 | freeline/ 175 | freeline_project_description.json 176 | 177 | # fastlane 178 | fastlane/report.xml 179 | fastlane/Preview.html 180 | fastlane/screenshots 181 | fastlane/test_output 182 | fastlane/readme.md 183 | /.idea/ 184 | ### Gradle template 185 | 186 | # Ignore Gradle GUI config 187 | 188 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 189 | 190 | # Cache of project 191 | 192 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 193 | # gradle/wrapper/gradle-wrapper.properties 194 | ### JetBrains template 195 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 196 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 197 | 198 | # User-specific stuff 199 | 200 | # Sensitive or high-churn files 201 | 202 | # Gradle 203 | 204 | # Gradle and Maven with auto-import 205 | # When using Gradle or Maven with auto-import, you should exclude module files, 206 | # since they will be recreated, and may cause churn. Uncomment if using 207 | # auto-import. 208 | # .idea/modules.xml 209 | # .idea/*.iml 210 | # .idea/modules 211 | 212 | # CMake 213 | 214 | # Mongo Explorer plugin 215 | 216 | # File-based project format 217 | 218 | # IntelliJ 219 | 220 | # mpeltonen/sbt-idea plugin 221 | 222 | # JIRA plugin 223 | 224 | # Cursive Clojure plugin 225 | 226 | # Crashlytics plugin (for Android Studio and IntelliJ) 227 | 228 | # Editor-based Rest Client 229 | ### Java template 230 | # Compiled class file 231 | 232 | # Log file 233 | 234 | # BlueJ files 235 | 236 | # Mobile Tools for Java (J2ME) 237 | 238 | # Package Files # 239 | 240 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 241 | ### Android template 242 | # Built application files 243 | 244 | # Files for the ART/Dalvik VM 245 | 246 | # Java class files 247 | 248 | # Generated files 249 | 250 | # Gradle files 251 | 252 | # Local configuration file (sdk path, etc) 253 | 254 | # Proguard folder generated by Eclipse 255 | 256 | # Log Files 257 | 258 | # Android Studio Navigation editor temp files 259 | 260 | # Android Studio captures folder 261 | 262 | # IntelliJ 263 | 264 | # Keystore files 265 | # Uncomment the following line if you do not want to check your keystore files in. 266 | #*.jks 267 | 268 | # External native build folder generated in Android Studio 2.2 and later 269 | 270 | # Google Services (e.g. APIs or Firebase) 271 | 272 | # Freeline 273 | 274 | # fastlane 275 | ### Kotlin template 276 | # Compiled class file 277 | 278 | # Log file 279 | 280 | # BlueJ files 281 | 282 | # Mobile Tools for Java (J2ME) 283 | 284 | # Package Files # 285 | 286 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 287 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ DEPRECATED 2 | This library is no longer maintained and will not be updated. 3 | 4 | 5 | # YuvToMat 6 | ### YUV_420_888 Image to OpenCV RGB Mat Conversion 7 | High-performance library for converting YUV_420_888 images from Android's Camera v2 API to OpenCV Mats. The resulting Mat contains RGB pixels. 8 | 9 | ## Usage 10 | ### Kotlin 11 | Simply use the extension function on android.media.Image. 12 | 13 | ```kotlin 14 | val mat = image.rgb() 15 | ``` 16 | 17 | ### Java 18 | Simply use the static function ```Yuv.toMat``` 19 | 20 | ```java 21 | Mat mat = Yuv.rgb(image) 22 | ``` 23 | 24 | ### Clipping the image 25 | This library supports efficient clipping of the image before converting. 26 | Just pass a clip to the function: 27 | 28 | ```kotlin 29 | val yuv = YuvImage(image).clip(left=20, top=20, right=40, bottom=40) 30 | val yuv = YuvImage(image).with(YuvImage.Clip(left=20, top=20, right=40, bottom=40)) 31 | val yuv = YuvImage(image) with YuvImage.Clip(left=20, top=20, right=40, bottom=40) 32 | val rgb: Mat = yuv.rgb() 33 | ``` 34 | 35 | ## Get the the dependency 36 | 37 | ### Gradle 38 | ``` 39 | dependencies { 40 | ... 41 | implementation "com.quickbirdstudios:yuvtomat:1.1.0" 42 | } 43 | ``` 44 | 45 | Also include the kotlin standard library for non-kotlin projects 46 | 47 | ``` 48 | dependencies { 49 | ... 50 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 51 | 52 | } 53 | ``` 54 | 55 | If you encounter any issues with this library, please submit an issue. We'll come back to you as soon as possible. 56 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 27 9 | defaultConfig { 10 | applicationId "com.quickbirdstudios.yuv2mat" 11 | minSdkVersion 19 12 | targetSdkVersion 27 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 33 | } 34 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/quickbirdstudios/yuv2mat/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.quickbirdstudios.yuv2mat", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/quickbirdstudios/yuv2mat/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | 6 | class MainActivity : AppCompatActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | yuv2mat 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/quickbirdstudios/yuv2mat/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.21' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.2' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | jcenter() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | 31 | 32 | if (!project.hasProperty("bintray_user")) { 33 | ext.bintray_user = "stub" 34 | } 35 | 36 | if (!project.hasProperty("bintray_apikey")) { 37 | ext.bintray_apikey = "stub" 38 | } 39 | 40 | if (!project.hasProperty("bintray_gpg_password")) { 41 | ext.bintray_gpg_password = "stub" 42 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuickBirdEng/yuvToMat/d53565c40b0cb0fceb6cc7e6acb20b00e153ee24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 18 19:12:31 CET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | 5 | ext { 6 | bintrayOrg = 'quickbirdstudios' 7 | bintrayRepo = 'android' 8 | bintrayName = 'yuvtomat' 9 | 10 | publishedGroupId = 'com.quickbirdstudios' 11 | libraryName = 'yuvtomat' 12 | artifact = 'yuvtomat' 13 | 14 | libraryDescription = 'Yuv Image Converter' 15 | 16 | gitUrl = 'https://github.com/quickbirdstudios/quickboot-android' 17 | siteUrl = 'http://quickbirdstudios.com' 18 | 19 | 20 | libraryVersion = '1.1.0' 21 | 22 | developerId = 'quickbirdstudios' 23 | developerName = 'QuickBird Studios GmbH' 24 | developerEmail = 'contact@quickbirdstudios.com' 25 | 26 | licenseName = 'The Apache Software License, Version 2.0' 27 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 28 | allLicenses = ["Apache-2.0"] 29 | } 30 | 31 | android { 32 | compileSdkVersion 27 33 | 34 | defaultConfig { 35 | minSdkVersion 19 36 | targetSdkVersion 27 37 | versionCode 1 38 | versionName "1.0" 39 | 40 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 41 | 42 | } 43 | 44 | buildTypes { 45 | release { 46 | minifyEnabled false 47 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 48 | } 49 | } 50 | 51 | packagingOptions { 52 | exclude 'META-INF/LICENSE' 53 | exclude 'META-INF/lib_release.kotlin_module' 54 | exclude 'META-INF/main.kotlin_module' 55 | } 56 | } 57 | 58 | dependencies { 59 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 60 | 61 | implementation "com.quickbirdstudios:opencv:3.4.1" 62 | testImplementation 'junit:junit:4.12' 63 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 64 | } 65 | 66 | 67 | apply plugin: 'com.github.dcendents.android-maven' 68 | group = publishedGroupId // Maven Group ID for the artifact 69 | 70 | 71 | apply plugin: 'com.jfrog.bintray' 72 | task sourceJar(type: Jar) { 73 | from android.sourceSets.main.java.srcDirs 74 | classifier "sources" 75 | } 76 | 77 | publishing { 78 | publications { 79 | aar(MavenPublication) { 80 | groupId = "$publishedGroupId" 81 | version = "$libraryVersion" 82 | artifactId = "$bintrayName" 83 | 84 | artifact("$buildDir/outputs/aar/lib.aar") 85 | artifact(sourceJar) 86 | } 87 | } 88 | } 89 | 90 | // Bintray 91 | bintray { 92 | user = "$bintray_user" 93 | key = "$bintray_apikey" 94 | publications = ['aar'] 95 | pkg { 96 | userOrg = "$bintrayOrg" 97 | repo = "$bintrayRepo" 98 | name = "$bintrayName" 99 | desc = "$libraryDescription" 100 | websiteUrl = "$siteUrl" 101 | vcsUrl = "$gitUrl" 102 | licenses = "$allLicenses" 103 | publish = true 104 | publicDownloadNumbers = true 105 | version { 106 | name = "$libraryVersion" 107 | desc = "$libraryVersion" 108 | } 109 | } 110 | } 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/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 22 | -------------------------------------------------------------------------------- /lib/src/androidTest/java/com/quickbirdstudios/yuv2mat/YuvI420ImageTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Before 6 | import org.junit.Test 7 | import org.opencv.android.OpenCVLoader 8 | import org.opencv.core.CvType 9 | import org.opencv.core.Mat 10 | import org.opencv.imgproc.Imgproc 11 | import java.nio.ByteBuffer 12 | 13 | /** 14 | * This test will use openCV to fabricate a synthetic yuv image in the 15 | * I410 format. 16 | * The image will be compared to the source rgb to check whether or not 17 | * conversion works as expected. 18 | */ 19 | class YuvI420ImageTest { 20 | 21 | private lateinit var rgbSource: Mat 22 | private lateinit var yuv: Yuv 23 | private lateinit var yuvImage: YuvImage 24 | 25 | @Before 26 | fun setup() { 27 | OpenCVLoader.initDebug() 28 | 29 | /* Arbitrary row and col count */ 30 | val rows = 100 31 | val cols = 100 32 | val pixels = rows * cols 33 | 34 | /* Create arbitrary rgb source mat */ 35 | rgbSource = Mat(rows, cols, CvType.CV_8UC3).forEach { row, col -> 36 | val r = row.toByte() 37 | val g = col.toByte() 38 | val b = (r + g / 2).toByte() 39 | put(row, col, byteArrayOf(r, g, b)) 40 | } 41 | 42 | /* Synthesize yuv data */ 43 | val yuvMat = Mat(rows + rows / 2, cols, CvType.CV_8UC1).also { yuvMat -> 44 | Imgproc.cvtColor(rgbSource, yuvMat, Imgproc.COLOR_RGB2YUV_I420) 45 | } 46 | 47 | val yuvData = ByteArray(pixels + pixels / 2).apply { 48 | yuvMat.get(0, 0, this) 49 | } 50 | 51 | val yData = yuvData.slice(0 until pixels).toByteArray() 52 | val uData = yuvData.slice(pixels until pixels + pixels / 4).toByteArray() 53 | val vData = yuvData.slice((pixels + pixels / 4) until pixels + pixels / 2).toByteArray() 54 | 55 | 56 | /* Create yuv object with correct strides from synthesized yuv data */ 57 | yuv = Yuv( 58 | width = cols, 59 | height = rows, 60 | y = Yuv.Plane( 61 | buffer = ByteBuffer.wrap(yData), 62 | pixelStride = 1, 63 | rowStride = cols), 64 | u = Yuv.Plane( 65 | buffer = ByteBuffer.wrap(uData), 66 | pixelStride = 1, 67 | rowStride = cols / 2), 68 | v = Yuv.Plane( 69 | buffer = ByteBuffer.wrap(vData), 70 | pixelStride = 1, 71 | rowStride = cols / 2)) 72 | 73 | 74 | yuvImage = YuvI420Image.create(yuv) ?: throw IllegalStateException("Wrong format") 75 | } 76 | 77 | 78 | @Test 79 | fun toRgb_matches_sourceRgb() { 80 | assertTrue(rgbSource.contentEquals(yuvImage.toRgb(), tolerance = 3.0)) 81 | } 82 | 83 | @Test 84 | fun clipToRgb_matches_submatSourceRgb() { 85 | val clip = YuvImage.Clip( 86 | left = 20, top = 20, right = 50, bottom = 50) 87 | 88 | val clippedYuvImage = yuvImage with clip 89 | val clippedRgb = clippedYuvImage.toRgb() 90 | 91 | val clippedRgbSource = rgbSource.submat( 92 | clip.top, clip.bottom, 93 | clip.left, clip.right) 94 | 95 | 96 | assertTrue(clippedRgbSource.contentEquals(clippedRgb, tolerance = 3.0)) 97 | } 98 | } -------------------------------------------------------------------------------- /lib/src/androidTest/java/com/quickbirdstudios/yuv2mat/YuvN12ImageTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.junit.Assert 4 | import org.junit.Before 5 | import org.junit.Test 6 | import org.opencv.android.OpenCVLoader 7 | import org.opencv.core.CvType 8 | import org.opencv.core.Mat 9 | import org.opencv.imgproc.Imgproc 10 | import java.nio.ByteBuffer 11 | 12 | /** 13 | * This test will use OpenCv to create synthetic yuv data. 14 | * The resulting [YuvImage] will then be compared to the source rgb 15 | */ 16 | class YuvN12ImageTest { 17 | 18 | private lateinit var rgbSource: Mat 19 | private lateinit var yuv: Yuv 20 | private lateinit var yuvImage: YuvImage 21 | 22 | @Before 23 | fun setup() { 24 | OpenCVLoader.initDebug() 25 | 26 | /* Arbitrary rows and cols */ 27 | val rows = 100 28 | val cols = 100 29 | val pixels = rows * cols 30 | 31 | /* Arbitrary rgb image */ 32 | rgbSource = Mat(rows, cols, CvType.CV_8UC3).forEach { row, col -> 33 | val r = row.toByte() 34 | val g = col.toByte() 35 | val b = (r + g / 2).toByte() 36 | put(row, col, byteArrayOf(r, g, b)) 37 | } 38 | 39 | /* 40 | Create yuv data. OpenCv only supports conversion in I420, which we 41 | then have to format as N12 42 | */ 43 | val yuvMat = Mat(rows + rows / 2, cols, CvType.CV_8UC1).also { yuvMat -> 44 | Imgproc.cvtColor(rgbSource, yuvMat, Imgproc.COLOR_RGB2YUV_I420) 45 | } 46 | 47 | 48 | /* Get Y, U and V values separately from the yuv mat */ 49 | val yuvData = ByteArray(pixels + pixels / 2).apply { 50 | yuvMat.get(0, 0, this) 51 | } 52 | 53 | val yData = yuvData.slice(0 until pixels).toByteArray() 54 | val uData = yuvData.slice(pixels until pixels + pixels / 4).toByteArray() 55 | val vData = yuvData.slice((pixels + pixels / 4) until pixels + pixels / 2).toByteArray() 56 | 57 | 58 | /* Convert separate Y, U and V values into N12 interleaved format */ 59 | val uvData = run { 60 | val data = ByteArray(pixels / 2) 61 | for (i in 0 until data.size) { 62 | data[i] = if (i % 2 == 0) uData[i / 2] else vData[(i - 1) / 2] 63 | } 64 | data 65 | } 66 | 67 | val vuData = run { 68 | val data = ByteArray(pixels / 2) 69 | for (i in 0 until data.size) { 70 | data[i] = if (i % 2 == 0) vData[i / 2] else uData[(i - 1) / 2] 71 | } 72 | data 73 | } 74 | 75 | 76 | /* Create yuv object with n12 supported strides */ 77 | yuv = Yuv( 78 | width = cols, 79 | height = rows, 80 | y = Yuv.Plane( 81 | buffer = ByteBuffer.wrap(yData), 82 | pixelStride = 1, 83 | rowStride = cols), 84 | u = Yuv.Plane( 85 | buffer = ByteBuffer.wrap(uvData), 86 | pixelStride = 2, 87 | rowStride = cols), 88 | v = Yuv.Plane( 89 | buffer = ByteBuffer.wrap(vuData), 90 | pixelStride = 2, 91 | rowStride = cols)) 92 | 93 | 94 | yuvImage = YuvN12Image.create(yuv) ?: throw IllegalStateException("Wrong format") 95 | } 96 | 97 | 98 | @Test 99 | fun toRgb_matches_sourceRgb() { 100 | Assert.assertTrue(rgbSource.contentEquals(yuvImage.toRgb(), tolerance = 3.0)) 101 | } 102 | 103 | @Test 104 | fun clipToRgb_matches_submatSourceRgb() { 105 | val clip = YuvImage.Clip( 106 | left = 20, top = 20, right = 50, bottom = 50) 107 | 108 | val clippedYuvImage = yuvImage with clip 109 | val clippedRgb = clippedYuvImage.toRgb() 110 | 111 | val clippedRgbSource = rgbSource.submat( 112 | clip.top, clip.bottom, 113 | clip.left, clip.right) 114 | 115 | 116 | Assert.assertTrue(clippedRgbSource.contentEquals(clippedRgb, tolerance = 3.0)) 117 | } 118 | } -------------------------------------------------------------------------------- /lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/BufferMatrix+with+Clip.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | 4 | /* 5 | ################################################################################################ 6 | INTERNAL API 7 | ################################################################################################ 8 | */ 9 | 10 | internal infix fun BufferMatrix.with(clip: YuvImage.Clip): BufferMatrix { 11 | val pixels = clip.width.value * clip.height.value 12 | val columnRange = clip.left until clip.right 13 | val clipped = byteBuffer(this.data.isDirect, pixels) 14 | for (row in clip.top until clip.bottom) { 15 | val rowBuffer = this[row, columnRange] 16 | clipped.put(rowBuffer) 17 | } 18 | 19 | return BufferMatrix( 20 | data = clipped, 21 | width = clip.width.value, 22 | height = clip.height.value) 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/BufferMatrix.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /* 6 | ################################################################################################ 7 | INTERNAL API 8 | ################################################################################################ 9 | */ 10 | 11 | /** 12 | * Helper class to wrap a given [ByteBuffer] into matrix representation. 13 | * This class is useful to retrieve data from the buffer under given row and col 14 | */ 15 | internal class BufferMatrix( 16 | val data: ByteBuffer, 17 | val width: Int, 18 | val height: Int) { 19 | 20 | val pixels = width * height 21 | 22 | operator fun get(row: Int, col: IntRange): ByteBuffer { 23 | val firstIndexOfRow = width * row 24 | val firstColumn = firstIndexOfRow + col.start 25 | val lastColumn = firstIndexOfRow + col.last 26 | return data.clip(firstColumn..lastColumn) 27 | } 28 | 29 | operator fun get(row: Int, col: Int): ByteBuffer { 30 | val firstIndexOfRow = width * row 31 | val index = firstIndexOfRow + col 32 | return data.clip(index..index) 33 | } 34 | 35 | init { 36 | if (pixels != data.limit()) { 37 | IllegalArgumentException("Expected width*height==data.limit(). " + 38 | "width*height=$pixels, data.limit()=${data.limit()}") 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/ByteBuffer+clip.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /* 6 | ################################################################################################ 7 | INTERNAL API 8 | ################################################################################################ 9 | */ 10 | 11 | internal fun ByteBuffer.clip(range: IntRange): ByteBuffer { 12 | val duplicate = this.duplicate() 13 | duplicate.position(range.start) 14 | duplicate.limit(range.endInclusive + 1) 15 | return duplicate.slice() 16 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/ByteBuffer+toByteArray.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /* 6 | ################################################################################################ 7 | INTERNAL API 8 | ################################################################################################ 9 | */ 10 | 11 | internal fun ByteBuffer.toByteArray(): ByteArray { 12 | return ByteArray(limit()).apply { 13 | val source = duplicate() 14 | source.position(0) 15 | source.get(this) 16 | } 17 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Clip+Math.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | /* 4 | ################################################################################################ 5 | PUBLIC API 6 | ################################################################################################ 7 | */ 8 | 9 | operator fun YuvImage.Clip.times(value: Int): YuvImage.Clip { 10 | return YuvImage.Clip( 11 | left = this.left * value, 12 | top = this.top * value, 13 | right = this.right * value, 14 | bottom = this.bottom * value) 15 | } 16 | 17 | operator fun YuvImage.Clip.div(value: Int): YuvImage.Clip { 18 | return YuvImage.Clip( 19 | left = this.left / value, 20 | top = this.top / value, 21 | right = this.right / value, 22 | bottom = this.bottom / value) 23 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Clip+adjustToYuvGrid.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | 4 | /* 5 | ################################################################################################ 6 | INTERNAL API 7 | ################################################################################################ 8 | */ 9 | 10 | /** 11 | * Adjusts the clip to the bounds of the 4x4 pixel grid of the yuv 420 format. 12 | * Arbitrary clipping is not allowed, since u and v values are sub-sampled in 4x4 pixel blocks. 13 | */ 14 | internal fun YuvImage.Clip.adjustToYuvGrid(): YuvImage.Clip { 15 | fun Int.isInGrid() = this % 2 == 0 16 | return YuvImage.Clip( 17 | left = if (this.left.isInGrid()) this.left else this.left - 1, 18 | top = if (this.top.isInGrid()) this.top else this.top - 1, 19 | right = if (this.right.isInGrid()) this.right else right + 1, 20 | bottom = if (this.bottom.isInGrid()) this.bottom else this.bottom + 1) 21 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Image+toMat.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.media.Image 4 | import org.opencv.core.Mat 5 | import android.graphics.ImageFormat 6 | 7 | /* 8 | ################################################################################################ 9 | PUBLIC API 10 | ################################################################################################ 11 | */ 12 | 13 | /** 14 | * Most convenient way of converting Yuv420_888 frames of Android into 15 | * an OpenCV RGB [Mat] 16 | * 17 | * @see ImageFormat.YUV_420_888 18 | */ 19 | fun Image.rgb(): Mat { 20 | return YuvImage(this).toRgb() 21 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Mat+contentEquals.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.opencv.core.Mat 4 | import kotlin.math.absoluteValue 5 | 6 | /* 7 | ################################################################################################ 8 | INTERNAL API 9 | ################################################################################################ 10 | */ 11 | 12 | internal fun Mat.contentEquals(other: Mat, tolerance: Double = 0.0): Boolean { 13 | if (this.rows() != other.rows()) return false 14 | if (this.cols() != other.cols()) return false 15 | if (this.channels() != other.channels()) return false 16 | 17 | this.forEach { row, col -> 18 | val thisData = this.get(row, col) 19 | val otherData = other.get(row, col) 20 | 21 | if (thisData.size != otherData.size) { 22 | return false 23 | } 24 | 25 | for (i in 0 until thisData.size) { 26 | val thisValue = thisData[i] 27 | val otherValue = otherData[i] 28 | if (thisValue.minus(otherValue).absoluteValue > tolerance) { 29 | return false 30 | } 31 | } 32 | } 33 | 34 | return true 35 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Mat+forEach.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.opencv.core.Mat 4 | 5 | /* 6 | ################################################################################################ 7 | INTERNAL API 8 | ################################################################################################ 9 | */ 10 | 11 | internal inline fun Mat.forEach(block: Mat.(row: Int, col: Int) -> Unit): Mat { 12 | for (row in 0 until rows()) { 13 | for (col in 0 until cols()) { 14 | block(row, col) 15 | } 16 | } 17 | 18 | return this 19 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Yuv+defrag.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.util.Log 4 | import java.nio.ByteBuffer 5 | 6 | /* 7 | ################################################################################################ 8 | PUBLIC API 9 | ################################################################################################ 10 | */ 11 | 12 | /** 13 | * @return [Yuv] with defragmented U and V planes. 14 | * @see defragPlane 15 | */ 16 | fun Yuv.defrag(): Yuv { 17 | val start = System.currentTimeMillis() 18 | 19 | require(y.pixelStride == 1) 20 | require(y.rowStride == width) 21 | 22 | val defragmented = copy( 23 | u = defragPlane(this.rows / 2, this.cols / 2, u), 24 | v = defragPlane(this.rows / 2, this.cols / 2, v)) 25 | 26 | val time = System.currentTimeMillis() - start 27 | Log.w(YuvImage::class.java.simpleName, "de-fragmentation took ${time}ms") 28 | 29 | return defragmented 30 | } 31 | 32 | 33 | /** 34 | * @return [Yuv.Plane] with 35 | * pixel stride = 1 36 | * and 37 | * rowStride = cols 38 | */ 39 | private fun defragPlane(rows: Int, cols: Int, plane: Yuv.Plane): Yuv.Plane { 40 | /* Return the plane if it is not even fragmented */ 41 | if (plane.pixelStride == 1 && plane.rowStride == cols) return plane 42 | 43 | /* Only remove padding */ 44 | if (plane.pixelStride == 1) return defragPlanePadding(rows, cols, plane) 45 | 46 | /* Remove padding and pixel stride */ 47 | return defragPlaneFull(rows, cols, plane) 48 | } 49 | 50 | private fun defragPlaneFull(rows: Int, cols: Int, plane: Yuv.Plane): Yuv.Plane { 51 | /* 52 | No move all data into the new defragmented ByteArray 53 | */ 54 | val defragmented = ByteArray(rows * cols) 55 | val data = plane.buffer.toByteArray() 56 | 57 | for (row in 0 until rows) { 58 | for (col in 0 until cols) { 59 | val index = row * plane.rowStride + col * plane.pixelStride 60 | defragmented[row * cols + col] = data[index] 61 | } 62 | } 63 | 64 | return Yuv.Plane( 65 | buffer = ByteBuffer.wrap(defragmented), 66 | pixelStride = 1, 67 | rowStride = cols) 68 | } 69 | 70 | private fun defragPlanePadding(rows: Int, cols: Int, plane: Yuv.Plane): Yuv.Plane { 71 | require(plane.pixelStride == 1) 72 | val planeMatrix = BufferMatrix(plane.buffer, width = plane.rowStride, height = rows) 73 | val defragmented = byteBuffer(plane.buffer.isDirect, rows * cols) 74 | val colRange = 0 until cols 75 | 76 | for (row in 0 until rows) { 77 | val rowData = planeMatrix[row, colRange] 78 | defragmented.put(rowData) 79 | } 80 | 81 | return Yuv.Plane( 82 | buffer = defragmented, 83 | pixelStride = 1, 84 | rowStride = cols) 85 | } 86 | 87 | 88 | //endregion 89 | -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/Yuv.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.graphics.ImageFormat 4 | import android.media.Image 5 | import java.nio.ByteBuffer 6 | 7 | /* 8 | ################################################################################################ 9 | PUBLIC API 10 | ################################################################################################ 11 | */ 12 | 13 | data class Yuv( 14 | val resource: AutoCloseable? = null, 15 | val width: Int, 16 | val height: Int, 17 | val y: Plane, 18 | val u: Plane, 19 | val v: Plane) : YuvPlanes, AutoCloseable { 20 | 21 | constructor(image: Image) : this( 22 | resource = image, 23 | width = image.width, 24 | height = image.height, 25 | y = Plane(image.planes[0]), 26 | u = Plane(image.planes[1]), 27 | v = Plane(image.planes[2])) { 28 | require(image.format == ImageFormat.YUV_420_888) 29 | require(image.width % 2 == 0) 30 | require(image.height % 2 == 0) 31 | } 32 | 33 | data class Plane( 34 | val buffer: ByteBuffer, 35 | val pixelStride: Int, 36 | val rowStride: Int) { 37 | constructor(plane: Image.Plane) : this( 38 | buffer = plane.buffer, 39 | pixelStride = plane.pixelStride, 40 | rowStride = plane.rowStride) 41 | } 42 | 43 | 44 | override fun close() { 45 | resource?.close() 46 | } 47 | 48 | val rows = this.height 49 | val cols = this.width 50 | 51 | 52 | /** 53 | * For java API call site 54 | */ 55 | companion object { 56 | @JvmStatic 57 | fun rgb(image: Image) = image.rgb() 58 | } 59 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/YuvI420Image.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.opencv.core.CvType 4 | import org.opencv.core.Mat 5 | import org.opencv.imgproc.Imgproc 6 | import java.nio.ByteBuffer 7 | 8 | /* 9 | ################################################################################################ 10 | INTERNAL API 11 | ################################################################################################ 12 | */ 13 | 14 | /** 15 | * Planar image format with all y values in a row followed by all u values in a row 16 | * followed by al v values in a row: 17 | * 18 | * ``` 19 | * y y y y y y 20 | * y y y y y y 21 | * y y y y y y 22 | * y y y y y y 23 | * u u u u u u 24 | * v v v v v v 25 | * ``` 26 | * 27 | * This implementation expects [yuv] to 28 | * have a pixel stride of 1 for all planes 29 | * as well as no padding suggested by the row stride. 30 | * 31 | * One can use [defrag] to bring yuv data into this format 32 | * 33 | * see [http://www.fourcc.org/pixel-format/yuv-i420/] for more information 34 | */ 35 | internal class YuvI420Image private constructor(private val yuv: Yuv) : YuvImage, YuvPlanes { 36 | 37 | override fun with(clip: YuvImage.Clip): YuvImage { 38 | /* yClip: Clipping has to be adjusted to the 4x4 sub-sampled yuv grid*/ 39 | val yClip = clip.adjustToYuvGrid() 40 | 41 | // U/V space has half the rows and half the columns 42 | val uClip = yClip / 2 43 | val vClip = yClip / 2 44 | 45 | 46 | 47 | val yMatrix = BufferMatrix( 48 | data = yuv.y.buffer, width = yuv.width, height = yuv.height) 49 | 50 | /* U/V only has half the rows and half the columns*/ 51 | val uMatrix = BufferMatrix( 52 | data = yuv.u.buffer, width = yuv.width / 2, height = yuv.height / 2) 53 | val vMatrix = BufferMatrix( 54 | data = yuv.v.buffer, width = yuv.width / 2, height = yuv.height / 2) 55 | 56 | 57 | /* Clip the raw data */ 58 | val yMatrixClipped = yMatrix with yClip 59 | val uMatrixClipped = uMatrix with uClip 60 | val vMatrixClipped = vMatrix with vClip 61 | 62 | 63 | /* Create image with correct strides */ 64 | return YuvI420Image( 65 | Yuv( 66 | resource = yuv, 67 | width = yClip.width.value, 68 | height = yClip.height.value, 69 | y = Yuv.Plane( 70 | buffer = yMatrixClipped.data, 71 | pixelStride = 1, 72 | rowStride = yClip.width.value), 73 | u = Yuv.Plane( 74 | buffer = uMatrixClipped.data, 75 | pixelStride = 1, 76 | rowStride = yClip.width.value / 2), 77 | v = Yuv.Plane( 78 | buffer = vMatrixClipped.data, 79 | pixelStride = 1, 80 | rowStride = yClip.width.value / 2))) 81 | } 82 | 83 | override fun toRgb(): Mat { 84 | 85 | /* 86 | Put all data into one ByteBuffer 87 | */ 88 | 89 | val y = yuv.y.buffer 90 | val u = yuv.u.buffer 91 | val v = yuv.v.buffer 92 | 93 | y.position(0) 94 | u.position(0) 95 | v.position(0) 96 | 97 | val data = ByteBuffer.allocateDirect( 98 | y.remaining() + u.remaining() + v.remaining()) 99 | 100 | data.put(y) 101 | data.put(u) 102 | data.put(v) 103 | 104 | 105 | /* 106 | Load the data into a mat. 107 | Since we have 1/4th the amount of u and v values than we have y values, 108 | the total height of the mat has to be the image height + 1/2 * image height 109 | */ 110 | val yuvMat = Mat(yuv.rows + yuv.rows / 2, yuv.cols, CvType.CV_8UC1, data) 111 | 112 | 113 | /* 114 | Create the rgb mat with correct dimensions and type and 115 | use Improc to convert the color space 116 | */ 117 | val rgbMat = Mat(yuv.rows, yuv.cols, CvType.CV_8UC3) 118 | Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2RGB_I420) 119 | 120 | 121 | yuvMat.release() 122 | return rgbMat 123 | } 124 | 125 | override fun close() = yuv.close() 126 | 127 | companion object Factory : YuvImageFactory { 128 | override fun create(yuv: Yuv): YuvImage? { 129 | if ( 130 | /* Y plane must be padding free */ 131 | yuv.y.rowStride == yuv.width && 132 | 133 | /* Y plane must be compact */ 134 | yuv.y.pixelStride == 1 && 135 | 136 | /* U plane must be compact */ 137 | yuv.u.pixelStride == 1 && 138 | 139 | /* U plane must be padding free */ 140 | yuv.u.rowStride == yuv.width / 2 && 141 | 142 | /* V plane must be compact */ 143 | yuv.v.pixelStride == 1 && 144 | 145 | /* V plane must be padding free */ 146 | yuv.v.rowStride == yuv.width / 2) { 147 | return YuvI420Image(yuv) 148 | } 149 | 150 | return null 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/YuvImage.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.graphics.ImageFormat 4 | import android.media.Image 5 | import org.opencv.core.Mat 6 | 7 | /* 8 | ################################################################################################ 9 | PUBLIC API 10 | ################################################################################################ 11 | */ 12 | 13 | /** 14 | * Represents an image taken in the yuv 420_888 format ([ImageFormat.YUV_420_888]). 15 | * Implementations are intended to support high efficiency conversion into 16 | * the rgb color space. 17 | * 18 | * 19 | * ### Pre-Clipping 20 | * Using the [clip] function before converting into rgb color space is generally preferred than 21 | * vice versa: 22 | * 23 | * ``` 24 | * YuvImage(image).clip(...).toRgb() 25 | * ``` 26 | * 27 | * Is often much faster than 28 | * 29 | * ``` 30 | * YuvImage(image).toRgb().submat(...) 31 | * ``` 32 | */ 33 | interface YuvImage : AutoCloseable { 34 | 35 | infix fun with(clip: Clip): YuvImage 36 | 37 | fun clip(left: Int, top: Int, right: Int, bottom: Int): YuvImage = 38 | this with Clip(left = left, top = top, right = right, bottom = bottom) 39 | 40 | fun toRgb(): Mat 41 | 42 | data class Clip(val left: Int, val top: Int, val right: Int, val bottom: Int) { 43 | 44 | interface Dimension { 45 | val value: Int 46 | operator fun times(value: Int): Clip 47 | operator fun div(value: Int): Clip 48 | } 49 | 50 | 51 | val width = object : Dimension { 52 | 53 | override val value: Int = right - left 54 | 55 | override fun times(value: Int): Clip = copy( 56 | left = left * value, 57 | right = right * value) 58 | 59 | override fun div(value: Int): Clip = copy( 60 | left = left / value, 61 | right = right / value) 62 | } 63 | 64 | val height = object : Dimension { 65 | 66 | override val value: Int = bottom - top 67 | 68 | override fun times(value: Int): Clip = copy( 69 | top = top * value, 70 | bottom = bottom * value) 71 | 72 | 73 | override fun div(value: Int): Clip = copy( 74 | top = top / value, 75 | bottom = bottom / value) 76 | 77 | } 78 | 79 | 80 | init { 81 | require(left < right) 82 | require(top < bottom) 83 | require(left >= 0) 84 | require(top >= 0) 85 | require(right >= 0) 86 | require(bottom >= 0) 87 | } 88 | } 89 | 90 | companion object Factory 91 | } 92 | 93 | 94 | /** 95 | * Thrown by [YuvImage.Factory.invoke] for non supported yuv formats. 96 | * According to the Android documentation no phone should encounter this exception. 97 | */ 98 | class UnsupportedImageFormatException(yuv: Yuv) : Exception("" + 99 | "Yuv format is not supported: $yuv") 100 | 101 | 102 | /** 103 | * Same as 104 | * ``` 105 | * YuvImage(Yuv(image)) 106 | * ``` 107 | */ 108 | @Throws(UnsupportedImageFormatException::class) 109 | operator fun YuvImage.Factory.invoke(image: Image): YuvImage { 110 | val yuv = Yuv(image) 111 | return YuvImage(yuv) 112 | } 113 | 114 | /** 115 | * @return The best performing implementation of [YuvImage] for the given data format 116 | * @throws UnsupportedImageFormatException: Not expected when captured by an android device 117 | */ 118 | @Throws(UnsupportedImageFormatException::class) 119 | operator fun YuvImage.Factory.invoke(yuv: Yuv): YuvImage { 120 | if (yuv.y.pixelStride != 1 || yuv.y.rowStride != yuv.width) { 121 | throw UnsupportedImageFormatException(yuv) 122 | } 123 | 124 | return YuvN12Image.create(yuv) 125 | ?: YuvI420Image.create(yuv) 126 | ?: YuvI420Image.create(yuv.defrag()) 127 | ?: throw UnsupportedImageFormatException(yuv) 128 | } 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/YuvImageFactory.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | /* 4 | ################################################################################################ 5 | INTERNAL API 6 | ################################################################################################ 7 | */ 8 | 9 | internal interface YuvImageFactory : YuvPlanes { 10 | fun create(yuv: Yuv): YuvImage? 11 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/YuvN12Image.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.opencv.core.CvType 4 | import org.opencv.core.Mat 5 | import org.opencv.imgproc.Imgproc 6 | import java.nio.ByteBuffer 7 | 8 | 9 | /* 10 | ################################################################################################ 11 | INTERNAL API 12 | ################################################################################################ 13 | */ 14 | 15 | /** 16 | * Semi-Planar image format with all y values in a row followed by interleaved u/v values 17 | * 18 | * ``` 19 | * y y y y y y 20 | * y y y y y y 21 | * y y y y y y 22 | * y y y y y y 23 | * u v u v u v 24 | * v v v v u v 25 | * `` 26 | * 27 | * ### Android 28 | * This implementation exploits the fact that android devices 29 | * that report a pixel stride of 2 in the u and v plane 30 | * have the u plane interleaved with the v plane. 31 | * 32 | * That means, that the y plane with an appended u plane gives the 33 | * YuvN12 Format described above 34 | * 35 | * 36 | * see [http://www.fourcc.org/pixel-format/yuv-nv12/] for more information 37 | */ 38 | internal class YuvN12Image private constructor(private val yuv: Yuv) : YuvImage, YuvPlanes { 39 | 40 | override fun with(clip: YuvImage.Clip): YuvImage { 41 | /* Adjust clip to the 4x4 sub-sampled yuv 420 grid */ 42 | val yClip = clip.adjustToYuvGrid() 43 | 44 | /* U/V Plane has the same width as the yPlane but just half the height */ 45 | val uvClip = yClip.height / 2 46 | val yMatrix = BufferMatrix(yuv.y.buffer, width = yuv.width, height = yuv.height) 47 | val uvMatrix = BufferMatrix(yuv.u.buffer, width = yuv.width, height = yuv.height / 2) 48 | val yMatrixClipped = yMatrix with yClip 49 | val uvMatrixClipped = uvMatrix with uvClip 50 | 51 | /* Create image with correct strides */ 52 | return YuvN12Image( 53 | Yuv( 54 | resource = yuv, 55 | width = yClip.width.value, 56 | height = yClip.height.value, 57 | y = Yuv.Plane( 58 | buffer = yMatrixClipped.data, 59 | pixelStride = 1, 60 | rowStride = yClip.width.value), 61 | u = Yuv.Plane( 62 | buffer = uvMatrixClipped.data, 63 | pixelStride = 2, 64 | rowStride = yClip.width.value), 65 | 66 | /* V Plane can be ignored and does not need to be clipped */ 67 | v = Yuv.Plane( 68 | buffer = ByteBuffer.allocate(0), 69 | pixelStride = 2, 70 | rowStride = yClip.width.value))) 71 | } 72 | 73 | override fun toRgb(): Mat { 74 | 75 | /* 76 | Load Y and U plane into one buffer. 77 | The V plane can be ignored, since the U plane contains all information 78 | of the V plane in its interleaved format (u1 v1 u2 v2, ...) 79 | */ 80 | 81 | val y = yuv.y.buffer 82 | val u = yuv.u.buffer 83 | 84 | y.position(0) 85 | u.position(0) 86 | 87 | val data = java.nio.ByteBuffer.allocateDirect(y.remaining() + u.remaining()) 88 | data.put(y) 89 | data.put(u) 90 | data.position(0) 91 | 92 | /* 93 | Load the data into a mat. 94 | Since we have 1/4th the amount of u and v values than we have y values, 95 | the total height of the mat has to be the image height + 1/2 * image height 96 | */ 97 | val yuvMat = Mat(yuv.rows + yuv.rows / 2, yuv.width, CvType.CV_8UC1, data) 98 | 99 | 100 | /* 101 | Create the rgb mat with correct dimensions and type and 102 | use Improc to convert the color space 103 | */ 104 | val rgbMat = Mat(yuv.rows, yuv.cols, CvType.CV_8UC3) 105 | Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2RGB_NV12) 106 | 107 | 108 | yuvMat.release() 109 | return rgbMat 110 | } 111 | 112 | override fun close() { 113 | yuv.close() 114 | } 115 | 116 | 117 | companion object Factory : YuvImageFactory { 118 | override fun create(yuv: Yuv): YuvImage? { 119 | if ( 120 | yuv.height % 2 == 0 && 121 | yuv.width % 2 == 0 && 122 | yuv.y.pixelStride == 1 && 123 | yuv.y.rowStride == yuv.width && 124 | yuv.u.pixelStride == 2 && 125 | yuv.u.rowStride == yuv.width && 126 | yuv.v.pixelStride == 2 && 127 | yuv.v.rowStride == yuv.width) { 128 | return YuvN12Image(yuv) 129 | } 130 | 131 | return null 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/YuvPlanes.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import android.media.Image 4 | 5 | internal interface YuvPlanes { 6 | val Array.y get() = this[0] 7 | val Array.u get() = this[1] 8 | val Array.v get() = this[2] 9 | companion object : YuvPlanes 10 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/quickbirdstudios/yuv2mat/byteBuffer.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /* 6 | ################################################################################################ 7 | INTERNAL API 8 | ################################################################################################ 9 | */ 10 | 11 | internal fun byteBuffer(direct: Boolean, capacity: Int): ByteBuffer { 12 | return if (direct) ByteBuffer.allocateDirect(capacity) 13 | else ByteBuffer.allocate(capacity) 14 | } -------------------------------------------------------------------------------- /lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | lib 3 | 4 | -------------------------------------------------------------------------------- /lib/src/test/java/com/quickbirdstudios/yuv2mat/BufferMatrixWithClipTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.junit.Assert.assertArrayEquals 4 | import org.junit.Test 5 | import java.nio.ByteBuffer 6 | 7 | class BufferMatrixWithClipTest { 8 | @Test 9 | fun arbitraryClip() { 10 | val data = byteArrayOf( 11 | 0, 0, 0, 0, 0, 0, 12 | 1, 1, 1, 1, 1, 1, 13 | 2, 2, 3, 3, 4, 4, 14 | 5, 5, 6, 6, 7, 7, 15 | 8, 8, 8, 8, 8, 8) 16 | 17 | val matrix = BufferMatrix(data = ByteBuffer.wrap(data), width = 6, height = 5) 18 | val clip = YuvImage.Clip(left = 2, top = 2, right = 4, bottom = 4) 19 | val clipped = matrix.with(clip).data 20 | 21 | assertArrayEquals(byteArrayOf(3, 3, 6, 6), clipped.toByteArray()) 22 | } 23 | } -------------------------------------------------------------------------------- /lib/src/test/java/com/quickbirdstudios/yuv2mat/ByteBufferClipTest.kt: -------------------------------------------------------------------------------- 1 | package com.quickbirdstudios.yuv2mat 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | import java.nio.ByteBuffer 6 | 7 | class ByteBufferClipTest { 8 | 9 | @Test 10 | internal fun clipDirectBuffer_start2_end8() { 11 | val directBuffer = ByteBuffer.allocateDirect(12) 12 | directBuffer.put(byteArrayOf( 13 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) 14 | 15 | val clippedBuffer = directBuffer.clip(2 until 8) 16 | 17 | assertEquals(6, clippedBuffer.remaining()) 18 | 19 | assertEquals(2.b, clippedBuffer[0]) 20 | assertEquals(3.b, clippedBuffer[1]) 21 | assertEquals(4.b, clippedBuffer[2]) 22 | assertEquals(5.b, clippedBuffer[3]) 23 | assertEquals(6.b, clippedBuffer[4]) 24 | assertEquals(7.b, clippedBuffer[5]) 25 | } 26 | 27 | @Test 28 | internal fun clipDirectBuffer_start0_end1() { 29 | val directBuffer = ByteBuffer.allocateDirect(12) 30 | directBuffer.put(byteArrayOf( 31 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) 32 | 33 | val clippedBuffer = directBuffer.clip(0 until 1) 34 | 35 | assertEquals(1, clippedBuffer.remaining()) 36 | assertEquals(0.b, clippedBuffer[0]) 37 | } 38 | } 39 | 40 | private val Int.b get () = this.toByte() -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib' 2 | --------------------------------------------------------------------------------