├── .github └── workflows │ └── android.yml ├── .gitignore ├── Application ├── Application.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── de │ │ └── uwepost │ │ └── android │ │ └── deltacam │ │ ├── AutoFitTextureView.java │ │ ├── CameraActivity.java │ │ ├── CompareSizesByArea.java │ │ ├── DeltaCameraFragment.java │ │ ├── OnFrameAvailableListener.java │ │ ├── PseudoUUID.java │ │ ├── RenderscriptWrapper.java │ │ └── VerticalSeekBar.java │ ├── res │ ├── drawable-hdpi │ │ ├── darken.png │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ ├── lighten.png │ │ └── tile.9.png │ ├── drawable-mdpi │ │ ├── darken.png │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ ├── lighten.png │ │ └── slider_bg.png │ ├── drawable-xhdpi │ │ ├── darken.png │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ └── lighten.png │ ├── drawable-xxhdpi │ │ ├── darken.png │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ └── lighten.png │ ├── drawable │ │ ├── seekbar_thumb.xml │ │ └── seekbar_thumb_grey.xml │ ├── layout │ │ ├── activity_camera.xml │ │ └── fragment_camera2_basic.xml │ ├── values-sw600dp │ │ ├── template-dimens.xml │ │ └── template-styles.xml │ ├── values-v11 │ │ └── template-styles.xml │ ├── values-v21 │ │ ├── base-colors.xml │ │ └── base-template-styles.xml │ └── values │ │ ├── base-strings.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ ├── template-dimens.xml │ │ └── template-styles.xml │ └── rs │ └── yuv2rgb.rs ├── DeltaCamera.iml ├── LICENSE ├── README.md ├── build.gradle ├── gfx ├── deltacam-icon-1024.png ├── deltacam-icon-512.png ├── deltacam-icon-large.xcf ├── funktiongrafik1024x500.jpg └── funktiongrafik1024x500.xcf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── ants1.jpg ├── ants2.jpg ├── fountain.jpg └── lightpaint.png └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | /keystore-uwepost.de.jks 9 | /Application/build 10 | 11 | -------------------------------------------------------------------------------- /Application/Application.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /Application/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | maven { 6 | url "https://maven.google.com" 7 | } 8 | 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.2.1' 13 | } 14 | } 15 | 16 | apply plugin: 'com.android.application' 17 | 18 | repositories { 19 | jcenter() 20 | maven { 21 | url "https://maven.google.com" 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation 'com.android.support:support-v4:27.1.1' 28 | implementation 'com.android.support:support-v13:27.1.1' 29 | implementation 'com.android.support:cardview-v7:27.1.1' 30 | implementation 'com.android.support:appcompat-v7:27.1.1' 31 | } 32 | 33 | // The sample build uses multiple directories to 34 | // keep boilerplate and common code separate from 35 | // the main sample code. 36 | List dirs = [ 37 | 'main', // main sample code; look here for the interesting stuff. 38 | 'common', // components that are reused by multiple samples 39 | 'template'] // boilerplate code that is generated by the sample template process 40 | 41 | android { 42 | compileSdkVersion 27 43 | 44 | buildToolsVersion '27.0.3' 45 | 46 | defaultConfig { 47 | minSdkVersion 21 48 | targetSdkVersion 27 49 | 50 | versionCode 11 51 | versionName "1.1" 52 | 53 | renderscriptTargetApi 18 54 | renderscriptSupportModeEnabled false 55 | } 56 | 57 | signingConfigs { 58 | releaseConfig { 59 | storeFile file("../keystore-uwepost.de.jks") 60 | storePassword "kliej4uwzikusjdczhgfjkusrzhtrio8sduzti8s45euz56i87c45z6987cz" 61 | keyAlias "sign" 62 | keyPassword "" 63 | } 64 | } 65 | buildTypes { 66 | release { 67 | minifyEnabled false 68 | signingConfig signingConfigs.releaseConfig 69 | } 70 | } 71 | compileOptions { 72 | sourceCompatibility JavaVersion.VERSION_1_8 73 | targetCompatibility JavaVersion.VERSION_1_8 74 | } 75 | 76 | sourceSets { 77 | main { 78 | dirs.each { dir -> 79 | java.srcDirs "src/${dir}/java" 80 | res.srcDirs "src/${dir}/res" 81 | } 82 | } 83 | androidTest.setRoot('tests') 84 | androidTest.java.srcDirs = ['tests/src'] 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Application/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/AutoFitTextureView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.uwepost.android.deltacam; 18 | 19 | import android.content.Context; 20 | import android.util.AttributeSet; 21 | import android.view.TextureView; 22 | 23 | /** 24 | * A {@link TextureView} that can be adjusted to a specified aspect ratio. 25 | */ 26 | public class AutoFitTextureView extends TextureView { 27 | 28 | private int mRatioWidth = 0; 29 | private int mRatioHeight = 0; 30 | 31 | public AutoFitTextureView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public AutoFitTextureView(Context context, AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { 40 | super(context, attrs, defStyle); 41 | } 42 | 43 | /** 44 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 45 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that 46 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. 47 | * 48 | * @param width Relative horizontal size 49 | * @param height Relative vertical size 50 | */ 51 | public void setAspectRatio(int width, int height) { 52 | if (width < 0 || height < 0) { 53 | throw new IllegalArgumentException("Size cannot be negative."); 54 | } 55 | mRatioWidth = width; 56 | mRatioHeight = height; 57 | requestLayout(); 58 | } 59 | 60 | @Override 61 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 63 | int width = MeasureSpec.getSize(widthMeasureSpec); 64 | int height = MeasureSpec.getSize(heightMeasureSpec); 65 | if (0 == mRatioWidth || 0 == mRatioHeight) { 66 | setMeasuredDimension(width, height); 67 | } else { 68 | if (width < height * mRatioWidth / mRatioHeight) { 69 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 70 | } else { 71 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 72 | } 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/CameraActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Uwe Post 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.uwepost.android.deltacam; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.app.AppCompatActivity; 21 | 22 | public class CameraActivity extends AppCompatActivity { 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_camera); 28 | if (null == savedInstanceState) { 29 | getSupportFragmentManager().beginTransaction() 30 | .replace(R.id.container, DeltaCameraFragment.newInstance()) 31 | .commit(); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/CompareSizesByArea.java: -------------------------------------------------------------------------------- 1 | package de.uwepost.android.deltacam; 2 | 3 | import android.util.Size; 4 | 5 | import java.util.Comparator; 6 | 7 | /** 8 | * Compares two {@code Size}s based on their areas. 9 | */ 10 | class CompareSizesByArea implements Comparator { 11 | 12 | @Override 13 | public int compare(Size lhs, Size rhs) { 14 | // We cast here to ensure the multiplications won't overflow 15 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 16 | (long) rhs.getWidth() * rhs.getHeight()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/DeltaCameraFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Uwe Post 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.uwepost.android.deltacam; 18 | 19 | import android.Manifest; 20 | import android.app.Activity; 21 | import android.app.AlertDialog; 22 | import android.app.Dialog; 23 | import android.content.Context; 24 | import android.content.DialogInterface; 25 | import android.content.Intent; 26 | import android.content.pm.PackageManager; 27 | import android.content.res.Configuration; 28 | import android.graphics.Bitmap; 29 | import android.graphics.Canvas; 30 | import android.graphics.Color; 31 | import android.graphics.ImageFormat; 32 | import android.graphics.Matrix; 33 | import android.graphics.Paint; 34 | import android.graphics.Point; 35 | import android.graphics.Rect; 36 | import android.graphics.RectF; 37 | import android.graphics.SurfaceTexture; 38 | import android.hardware.camera2.CameraAccessException; 39 | import android.hardware.camera2.CameraCaptureSession; 40 | import android.hardware.camera2.CameraCharacteristics; 41 | import android.hardware.camera2.CameraDevice; 42 | import android.hardware.camera2.CameraManager; 43 | import android.hardware.camera2.CaptureRequest; 44 | import android.hardware.camera2.CaptureResult; 45 | import android.hardware.camera2.TotalCaptureResult; 46 | import android.hardware.camera2.params.StreamConfigurationMap; 47 | import android.net.Uri; 48 | import android.os.Bundle; 49 | import android.os.Handler; 50 | import android.os.HandlerThread; 51 | import android.provider.MediaStore; 52 | import android.renderscript.RenderScript; 53 | import android.support.annotation.NonNull; 54 | import android.support.v4.app.ActivityCompat; 55 | import android.support.v4.app.DialogFragment; 56 | import android.support.v4.app.Fragment; 57 | import android.support.v4.content.ContextCompat; 58 | import android.util.Log; 59 | import android.util.Range; 60 | import android.util.Rational; 61 | import android.util.Size; 62 | import android.util.SparseIntArray; 63 | import android.view.LayoutInflater; 64 | import android.view.Surface; 65 | import android.view.TextureView; 66 | import android.view.View; 67 | import android.view.ViewGroup; 68 | import android.widget.SeekBar; 69 | import android.widget.Toast; 70 | 71 | import java.io.ByteArrayOutputStream; 72 | import java.util.ArrayList; 73 | import java.util.Arrays; 74 | import java.util.Collections; 75 | import java.util.List; 76 | import java.util.concurrent.Semaphore; 77 | import java.util.concurrent.TimeUnit; 78 | 79 | public class DeltaCameraFragment extends Fragment implements View.OnClickListener, ActivityCompat.OnRequestPermissionsResultCallback { 80 | 81 | /** 82 | * Conversion from screen rotation to JPEG orientation. 83 | */ 84 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 85 | private static final int REQUEST_PERMISSIONS = 1; 86 | private static final String FRAGMENT_DIALOG = "dialog"; 87 | 88 | private static final int JPEG_QUALITY = 90; 89 | private static final int FPS = 100; 90 | 91 | private static Paint paL = new Paint(Paint.ANTI_ALIAS_FLAG); 92 | 93 | static { 94 | ORIENTATIONS.append(Surface.ROTATION_0, 90); 95 | ORIENTATIONS.append(Surface.ROTATION_90, 0); 96 | ORIENTATIONS.append(Surface.ROTATION_180, 270); 97 | ORIENTATIONS.append(Surface.ROTATION_270, 180); 98 | } 99 | 100 | 101 | private static final String TAG = "DiffCam"; 102 | 103 | /** 104 | * Camera state: Showing camera preview. 105 | */ 106 | private static final int STATE_PREVIEW = 0; 107 | 108 | /** 109 | * Camera state: Waiting for the focus to be locked. 110 | */ 111 | private static final int STATE_WAITING_LOCK = 1; 112 | 113 | /** 114 | * Camera state: Waiting for the exposure to be precapture state. 115 | */ 116 | private static final int STATE_WAITING_PRECAPTURE = 2; 117 | 118 | /** 119 | * Camera state: Waiting for the exposure state to be something other than precapture. 120 | */ 121 | private static final int STATE_WAITING_NON_PRECAPTURE = 3; 122 | 123 | /** 124 | * Max preview width that is guaranteed by Camera2 API 125 | */ 126 | private static final int MAX_PREVIEW_WIDTH = 1920; 127 | 128 | /** 129 | * Max preview height that is guaranteed by Camera2 API 130 | */ 131 | private static final int MAX_PREVIEW_HEIGHT = 1080; 132 | 133 | private RenderscriptWrapper mRenderscriptWrapper; 134 | 135 | private RenderScript mRS; 136 | 137 | 138 | private Rect mRectDest; 139 | boolean mVideoTexture; 140 | 141 | private double compensationStep; 142 | private int minCompensationRange; 143 | private int maxCompensationRange; 144 | 145 | private int exposureCompensation; 146 | 147 | private Rect mRectSrc; 148 | private Bitmap currentBitmap; 149 | 150 | /** 151 | * ID of the current {@link CameraDevice}. 152 | */ 153 | private String mCameraId; 154 | 155 | /** 156 | * An {@link AutoFitTextureView} for camera preview. 157 | */ 158 | private AutoFitTextureView mPreviewView; 159 | 160 | /** 161 | * A {@link CameraCaptureSession } for camera preview. 162 | */ 163 | private CameraCaptureSession mCaptureSession; 164 | 165 | /** 166 | * A reference to the opened {@link CameraDevice}. 167 | */ 168 | private CameraDevice mCameraDevice; 169 | 170 | /** 171 | * The {@link android.util.Size} of camera preview. 172 | */ 173 | private Size mPreviewSize; 174 | 175 | private Size mVideoSize; 176 | 177 | private VerticalSeekBar mSensitivitySeekbar,mExposureSeekbar; 178 | 179 | /** 180 | * An additional thread for running tasks that shouldn't block the UI. 181 | */ 182 | private HandlerThread mBackgroundThread; 183 | 184 | /** 185 | * A {@link Handler} for running tasks in the background. 186 | */ 187 | private Handler mBackgroundHandler; 188 | 189 | private AutoFitTextureView mDeltaCamView; 190 | 191 | /** 192 | * {@link CaptureRequest.Builder} for the camera preview 193 | */ 194 | private CaptureRequest.Builder mPreviewRequestBuilder; 195 | 196 | 197 | /** 198 | * The current state of camera state for taking pictures. 199 | * 200 | * @see #mCaptureCallback 201 | */ 202 | private int mState = STATE_PREVIEW; 203 | 204 | /** 205 | * A {@link Semaphore} to prevent the app from exiting before closing the camera. 206 | */ 207 | private Semaphore mCameraOpenCloseLock = new Semaphore(1); 208 | 209 | 210 | /** 211 | * Orientation of the camera sensor 212 | */ 213 | private int mSensorOrientation; 214 | 215 | 216 | /** 217 | * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 218 | * {@link TextureView}. 219 | */ 220 | private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { 221 | 222 | @Override 223 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 224 | openCamera(width, height); 225 | } 226 | 227 | @Override 228 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 229 | configureTransform(width, height); 230 | } 231 | 232 | @Override 233 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 234 | return true; 235 | } 236 | 237 | @Override 238 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 239 | } 240 | 241 | }; 242 | 243 | 244 | private TextureView.SurfaceTextureListener mVideoTextureListener = new TextureView.SurfaceTextureListener() { 245 | 246 | @Override 247 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 248 | mVideoTexture = true; 249 | } 250 | 251 | @Override 252 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { 253 | } 254 | 255 | @Override 256 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 257 | mVideoTexture = false; 258 | return true; 259 | } 260 | 261 | @Override 262 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 263 | } 264 | 265 | }; 266 | 267 | 268 | 269 | 270 | /** 271 | * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. 272 | */ 273 | private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 274 | 275 | @Override 276 | public void onOpened(@NonNull CameraDevice cameraDevice) { 277 | // This method is called when the camera is opened. We start camera preview here. 278 | mCameraOpenCloseLock.release(); 279 | mCameraDevice = cameraDevice; 280 | createCameraPreviewSession(); 281 | } 282 | 283 | @Override 284 | public void onDisconnected(@NonNull CameraDevice cameraDevice) { 285 | mCameraOpenCloseLock.release(); 286 | cameraDevice.close(); 287 | mCameraDevice = null; 288 | } 289 | 290 | @Override 291 | public void onError(@NonNull CameraDevice cameraDevice, int error) { 292 | mCameraOpenCloseLock.release(); 293 | cameraDevice.close(); 294 | mCameraDevice = null; 295 | Activity activity = getActivity(); 296 | if (null != activity) { 297 | activity.finish(); 298 | } 299 | } 300 | 301 | }; 302 | 303 | /** 304 | * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. 305 | */ 306 | private CameraCaptureSession.CaptureCallback mCaptureCallback 307 | = new CameraCaptureSession.CaptureCallback() { 308 | 309 | private void process(CaptureResult result) { 310 | switch (mState) { 311 | case STATE_PREVIEW: { 312 | // We have nothing to do when the camera preview is working normally. 313 | break; 314 | } 315 | case STATE_WAITING_LOCK: { 316 | 317 | break; 318 | } 319 | case STATE_WAITING_PRECAPTURE: { 320 | // CONTROL_AE_STATE can be null on some devices 321 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 322 | if (aeState == null || 323 | aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 324 | aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 325 | mState = STATE_WAITING_NON_PRECAPTURE; 326 | } 327 | break; 328 | } 329 | case STATE_WAITING_NON_PRECAPTURE: { 330 | 331 | break; 332 | } 333 | } 334 | } 335 | 336 | @Override 337 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 338 | @NonNull CaptureRequest request, 339 | @NonNull CaptureResult partialResult) { 340 | process(partialResult); 341 | } 342 | 343 | @Override 344 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 345 | @NonNull CaptureRequest request, 346 | @NonNull TotalCaptureResult result) { 347 | process(result); 348 | } 349 | 350 | }; 351 | 352 | 353 | 354 | /** 355 | * Shows a {@link Toast} on the UI thread. 356 | * 357 | * @param text The message to show 358 | */ 359 | private void showToast(final String text) { 360 | final Activity activity = getActivity(); 361 | if (activity != null) { 362 | activity.runOnUiThread(new Runnable() { 363 | @Override 364 | public void run() { 365 | Toast.makeText(activity, text, Toast.LENGTH_SHORT).show(); 366 | } 367 | }); 368 | } 369 | } 370 | 371 | /** 372 | * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that 373 | * is at least as large as the respective texture view size, and that is at most as large as the 374 | * respective max size, and whose aspect ratio matches with the specified value. If such size 375 | * doesn't exist, choose the largest one that is at most as large as the respective max size, 376 | * and whose aspect ratio matches with the specified value. 377 | * 378 | * @param choices The list of sizes that the camera supports for the intended output 379 | * class 380 | * @param textureViewWidth The width of the texture view relative to sensor coordinate 381 | * @param textureViewHeight The height of the texture view relative to sensor coordinate 382 | * @param maxWidth The maximum width that can be chosen 383 | * @param maxHeight The maximum height that can be chosen 384 | * @param aspectRatio The aspect ratio 385 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough 386 | */ 387 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 388 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 389 | 390 | // Collect the supported resolutions that are at least as big as the preview Surface 391 | List bigEnough = new ArrayList<>(); 392 | // Collect the supported resolutions that are smaller than the preview Surface 393 | List notBigEnough = new ArrayList<>(); 394 | int w = aspectRatio.getWidth(); 395 | int h = aspectRatio.getHeight(); 396 | for (Size option : choices) { 397 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight 398 | // && option.getHeight() == option.getWidth() * h / w 399 | ) { 400 | if (option.getWidth() >= textureViewWidth && 401 | option.getHeight() >= textureViewHeight) { 402 | bigEnough.add(option); 403 | } else { 404 | notBigEnough.add(option); 405 | } 406 | } 407 | } 408 | 409 | // Pick the smallest of those big enough. If there is no one big enough, pick the 410 | // largest of those not big enough. 411 | if (bigEnough.size() > 0) { 412 | return Collections.min(bigEnough, new CompareSizesByArea()); 413 | } else if (notBigEnough.size() > 0) { 414 | return Collections.max(notBigEnough, new CompareSizesByArea()); 415 | } else { 416 | Log.e(TAG, "Couldn't find any suitable preview size"); 417 | return choices[0]; 418 | } 419 | } 420 | 421 | public static DeltaCameraFragment newInstance() { 422 | return new DeltaCameraFragment(); 423 | } 424 | 425 | @Override 426 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 427 | Bundle savedInstanceState) { 428 | 429 | mRS = RenderScript.create(getActivity()); 430 | 431 | return inflater.inflate(R.layout.fragment_camera2_basic, container, false); 432 | } 433 | 434 | @Override 435 | public void onViewCreated(final View view, Bundle savedInstanceState) { 436 | view.findViewById(R.id.share).setOnClickListener(this); 437 | 438 | mPreviewView = (AutoFitTextureView) view.findViewById(R.id.preview); 439 | 440 | mDeltaCamView = (AutoFitTextureView) view.findViewById(R.id.cam); 441 | mDeltaCamView.setSurfaceTextureListener(mVideoTextureListener); 442 | 443 | mSensitivitySeekbar = (VerticalSeekBar) view.findViewById(R.id.sensitivity); 444 | mSensitivitySeekbar.setMax(255); 445 | mSensitivitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 446 | @Override 447 | public void onProgressChanged(SeekBar seekBar, int value, boolean b) { 448 | if(value>0) { 449 | Log.d(TAG, "setting threshold to " + value); 450 | if (mRenderscriptWrapper != null) 451 | mRenderscriptWrapper.setThreshold((short) value); 452 | } 453 | } 454 | 455 | @Override 456 | public void onStartTrackingTouch(SeekBar seekBar) { 457 | 458 | } 459 | 460 | @Override 461 | public void onStopTrackingTouch(SeekBar seekBar) { 462 | 463 | } 464 | }); 465 | 466 | mExposureSeekbar = view.findViewById(R.id.exposure_compensation); 467 | mExposureSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 468 | @Override 469 | public void onProgressChanged(SeekBar seekBar, int value, boolean b) { 470 | exposureCompensation = minCompensationRange + (maxCompensationRange-minCompensationRange)*value/100; 471 | if(mPreviewRequestBuilder!=null && mCaptureSession!=null) 472 | applyPreviewRequest(); 473 | } 474 | 475 | @Override 476 | public void onStartTrackingTouch(SeekBar seekBar) { 477 | 478 | } 479 | 480 | @Override 481 | public void onStopTrackingTouch(SeekBar seekBar) { 482 | 483 | } 484 | }); 485 | 486 | view.findViewById(R.id.reset).setOnClickListener(this); 487 | 488 | view.findViewById(R.id.lighten).setOnClickListener(this); 489 | view.findViewById(R.id.darken).setOnClickListener(this); 490 | 491 | 492 | } 493 | 494 | 495 | @Override 496 | public void onResume() { 497 | super.onResume(); 498 | startBackgroundThread(); 499 | 500 | // When the screen is turned off and turned back on, the SurfaceTexture is already 501 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open 502 | // a camera and start preview from here (otherwise, we wait until the surface is ready in 503 | // the SurfaceTextureListener). 504 | if (mDeltaCamView.isAvailable()) { 505 | openCamera(mDeltaCamView.getWidth(), mDeltaCamView.getHeight()); 506 | } else { 507 | mDeltaCamView.setSurfaceTextureListener(mSurfaceTextureListener); 508 | } 509 | } 510 | 511 | @Override 512 | public void onPause() { 513 | stopBackgroundThread(); 514 | super.onPause(); 515 | } 516 | 517 | @Override 518 | public void onDestroy() { 519 | closeCamera(); 520 | super.onDestroy(); 521 | } 522 | 523 | private void requestPermissions() { 524 | if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { 525 | new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG); 526 | } else { 527 | requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSIONS); 528 | } 529 | } 530 | 531 | @Override 532 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 533 | @NonNull int[] grantResults) { 534 | if (requestCode == REQUEST_PERMISSIONS) { 535 | if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { 536 | ErrorDialog.newInstance(getString(R.string.request_permission)) 537 | .show(getChildFragmentManager(), FRAGMENT_DIALOG); 538 | } 539 | } else { 540 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 541 | } 542 | } 543 | 544 | /** 545 | * Sets up member variables related to camera. 546 | * 547 | * @param width The width of available size for camera preview 548 | * @param height The height of available size for camera preview 549 | */ 550 | @SuppressWarnings("SuspiciousNameCombination") 551 | private void setUpCameraOutputs(int width, int height) { 552 | Activity activity = getActivity(); 553 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 554 | try { 555 | for (String cameraId : manager.getCameraIdList()) { 556 | CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); 557 | 558 | // We don't use a front facing camera in this sample. 559 | Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 560 | if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { 561 | continue; 562 | } 563 | 564 | StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 565 | if (map == null) { 566 | continue; 567 | } 568 | 569 | Rational controlAECompensationStep = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); 570 | if (controlAECompensationStep != null) { 571 | compensationStep = controlAECompensationStep.doubleValue(); 572 | } 573 | 574 | Range controlAECompensationRange = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); 575 | if (controlAECompensationRange != null) { 576 | minCompensationRange = controlAECompensationRange.getLower(); 577 | maxCompensationRange = controlAECompensationRange.getUpper(); 578 | } 579 | 580 | Log.i(TAG, "compensation range is " + minCompensationRange+".."+maxCompensationRange + ", step " + compensationStep); 581 | mExposureSeekbar.setProgress( 100*maxCompensationRange/(maxCompensationRange-minCompensationRange) ); 582 | 583 | // For still image captures, we use the largest available size. 584 | Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new CompareSizesByArea()); 585 | 586 | // Find out if we need to swap dimension to get the preview size relative to sensor 587 | // coordinate. 588 | int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 589 | //noinspection ConstantConditions 590 | mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 591 | boolean swappedDimensions = false; 592 | switch (displayRotation) { 593 | case Surface.ROTATION_0: 594 | case Surface.ROTATION_180: 595 | if (mSensorOrientation == 90 || mSensorOrientation == 270) { 596 | swappedDimensions = true; 597 | } 598 | break; 599 | case Surface.ROTATION_90: 600 | case Surface.ROTATION_270: 601 | if (mSensorOrientation == 0 || mSensorOrientation == 180) { 602 | swappedDimensions = true; 603 | } 604 | break; 605 | default: 606 | Log.e(TAG, "Display rotation is invalid: " + displayRotation); 607 | } 608 | 609 | Point displaySize = new Point(); 610 | activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 611 | int rotatedPreviewWidth = width; 612 | int rotatedPreviewHeight = height; 613 | int maxPreviewWidth = displaySize.x; 614 | int maxPreviewHeight = displaySize.y; 615 | 616 | if (swappedDimensions) { 617 | rotatedPreviewWidth = height; 618 | rotatedPreviewHeight = width; 619 | maxPreviewWidth = displaySize.y; 620 | maxPreviewHeight = displaySize.x; 621 | } 622 | 623 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 624 | maxPreviewWidth = MAX_PREVIEW_WIDTH; 625 | } 626 | 627 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 628 | maxPreviewHeight = MAX_PREVIEW_HEIGHT; 629 | } 630 | 631 | mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 632 | rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, 633 | maxPreviewHeight, largest); 634 | 635 | Log.d(TAG, "preview size: " + mPreviewSize + ", largest capture size: " + largest + ", rotated preview size: " + rotatedPreviewWidth + "," + rotatedPreviewHeight); 636 | 637 | mRectSrc = new Rect(0, 0, mPreviewSize.getWidth(), mPreviewSize.getHeight()); 638 | 639 | mVideoSize = new Size(mPreviewSize.getWidth(),mPreviewSize.getHeight()); 640 | mRectDest = new Rect(0, 0,mDeltaCamView.getWidth(), mDeltaCamView.getHeight()); 641 | 642 | mRenderscriptWrapper = new RenderscriptWrapper(mRS, mPreviewSize, buffer -> { 643 | // create bitmap from given buffer. This buffer relates to size mPreviewSize 644 | currentBitmap = Bitmap.createBitmap(buffer, mVideoSize.getWidth(), mVideoSize.getHeight(), Bitmap.Config.ARGB_8888); 645 | // draw this bitmap into canvas 646 | Canvas c = mDeltaCamView.lockCanvas(); 647 | if (c != null) { 648 | c.drawColor(Color.BLACK); 649 | c.drawBitmap(currentBitmap, mRectSrc, mRectDest, paL); 650 | mDeltaCamView.unlockCanvasAndPost(c); 651 | } 652 | }, 1000/FPS); 653 | 654 | mSensitivitySeekbar.setProgress(mRenderscriptWrapper.getThreshold()); 655 | 656 | // We fit the aspect ratio of TextureView to the size of preview we picked. 657 | int orientation = getResources().getConfiguration().orientation; 658 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 659 | mDeltaCamView.setAspectRatio( mPreviewSize.getWidth(), mPreviewSize.getHeight()); 660 | } else { 661 | mDeltaCamView.setAspectRatio( mPreviewSize.getHeight(), mPreviewSize.getWidth()); 662 | } 663 | 664 | mCameraId = cameraId; 665 | return; 666 | } 667 | } catch (CameraAccessException e) { 668 | e.printStackTrace(); 669 | } 670 | } 671 | 672 | /** 673 | * Opens the camera specified by {@link DeltaCameraFragment#mCameraId}. 674 | */ 675 | private void openCamera(int width, int height) { 676 | if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED 677 | || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 678 | ) { 679 | requestPermissions(); 680 | return; 681 | } 682 | Log.d(TAG, "openCamera " + width + "x" + height); 683 | setUpCameraOutputs(width, height); 684 | 685 | Activity activity = getActivity(); 686 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 687 | try { 688 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 689 | throw new RuntimeException("Time out waiting to lock camera opening."); 690 | } 691 | manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); 692 | } catch (CameraAccessException e) { 693 | e.printStackTrace(); 694 | } catch (InterruptedException e) { 695 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 696 | } 697 | } 698 | 699 | /** 700 | * Closes the current {@link CameraDevice}. 701 | */ 702 | private void closeCamera() { 703 | try { 704 | mCameraOpenCloseLock.acquire(); 705 | if (null != mCaptureSession) { 706 | mCaptureSession.close(); 707 | mCaptureSession = null; 708 | } 709 | if (null != mCameraDevice) { 710 | mCameraDevice.close(); 711 | mCameraDevice = null; 712 | } 713 | } catch (InterruptedException e) { 714 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 715 | } finally { 716 | mCameraOpenCloseLock.release(); 717 | } 718 | } 719 | 720 | /** 721 | * Starts a background thread and its {@link Handler}. 722 | */ 723 | private void startBackgroundThread() { 724 | mBackgroundThread = new HandlerThread("CameraBackground"); 725 | mBackgroundThread.start(); 726 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 727 | } 728 | 729 | /** 730 | * Stops the background thread and its {@link Handler}. 731 | */ 732 | private void stopBackgroundThread() { 733 | mBackgroundThread.quitSafely(); 734 | try { 735 | mBackgroundThread.join(); 736 | mBackgroundThread = null; 737 | mBackgroundHandler = null; 738 | } catch (InterruptedException e) { 739 | e.printStackTrace(); 740 | } 741 | } 742 | 743 | /** 744 | * Creates a new {@link CameraCaptureSession} for camera preview. 745 | */ 746 | private void createCameraPreviewSession() { 747 | try { 748 | 749 | List surfaces = new ArrayList<>(); 750 | 751 | // We set up a CaptureRequest.Builder with the output Surface. 752 | mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 753 | 754 | Surface inputSurface = mRenderscriptWrapper.getInputSurface(); 755 | surfaces.add(inputSurface); 756 | 757 | mPreviewRequestBuilder.addTarget(inputSurface); 758 | 759 | // Here, we create a CameraCaptureSession for camera preview. 760 | mCameraDevice.createCaptureSession(surfaces, 761 | new CameraCaptureSession.StateCallback() { 762 | 763 | @Override 764 | public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 765 | // The camera is already closed 766 | if (null == mCameraDevice) { 767 | return; 768 | } 769 | 770 | // When the session is ready, we start displaying the preview. 771 | mCaptureSession = cameraCaptureSession; 772 | applyPreviewRequest(); 773 | } 774 | 775 | @Override 776 | public void onConfigureFailed( @NonNull CameraCaptureSession cameraCaptureSession) { 777 | showToast("Failed to configure cameraCaptureSession"); 778 | } 779 | }, null 780 | ); 781 | } catch (CameraAccessException e) { 782 | e.printStackTrace(); 783 | } 784 | } 785 | 786 | private void applyPreviewRequest() { 787 | try { 788 | // Auto focus should be continuous for camera preview. 789 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 790 | 791 | // brightness 792 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION , exposureCompensation); 793 | 794 | // Finally, we start displaying the camera preview. 795 | mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); 796 | } catch (CameraAccessException e) { 797 | Log.e(TAG,"CameraAccessException",e); 798 | } 799 | } 800 | 801 | /** 802 | * Configures the necessary {@link android.graphics.Matrix} transformation to `mPreviewView`. 803 | * This method should be called after the camera preview size is determined in 804 | * setUpCameraOutputs and also the size of `mPreviewView` is fixed. 805 | * 806 | * @param viewWidth The width of `mPreviewView` 807 | * @param viewHeight The height of `mPreviewView` 808 | */ 809 | private void configureTransform(int viewWidth, int viewHeight) { 810 | Activity activity = getActivity(); 811 | if (null == mPreviewView || null == mDeltaCamView || null == activity) { 812 | return; 813 | } 814 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 815 | Log.d(TAG,"rotation "+rotation); 816 | Matrix matrix = new Matrix(); 817 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 818 | RectF bufferRect = new RectF(0, 0, mDeltaCamView.getHeight(), mDeltaCamView.getWidth()); 819 | float centerX = viewRect.centerX(); 820 | float centerY = viewRect.centerY(); 821 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 822 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 823 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 824 | float scale = Math.max( 825 | (float) viewHeight / mDeltaCamView.getHeight(), 826 | (float) viewWidth / mDeltaCamView.getWidth()); 827 | matrix.postScale(scale, scale, centerX, centerY); 828 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 829 | } else if (Surface.ROTATION_180 == rotation) { 830 | matrix.postRotate(180, centerX, centerY); 831 | } 832 | mPreviewView.setTransform(matrix); 833 | } 834 | 835 | 836 | 837 | 838 | @Override 839 | public void onClick(View view) { 840 | switch (view.getId()) { 841 | case R.id.lighten: { 842 | if(mRenderscriptWrapper!=null) mRenderscriptWrapper.setLighten(); 843 | break; 844 | } 845 | case R.id.darken: { 846 | if(mRenderscriptWrapper!=null) mRenderscriptWrapper.setDarken(); 847 | break; 848 | } 849 | case R.id.share: { 850 | if(currentBitmap!=null) shareBitmap(currentBitmap); 851 | break; 852 | } 853 | case R.id.reset: 854 | if(mRenderscriptWrapper!=null) mRenderscriptWrapper.reset(); 855 | break; 856 | case R.id.info: { 857 | Activity activity = getActivity(); 858 | if (null != activity) { 859 | new AlertDialog.Builder(activity) 860 | .setMessage(R.string.intro_message) 861 | .setPositiveButton(android.R.string.ok, null) 862 | .show(); 863 | } 864 | break; 865 | } 866 | } 867 | } 868 | 869 | 870 | 871 | /** 872 | * Shows an error message dialog. 873 | */ 874 | public static class ErrorDialog extends DialogFragment { 875 | 876 | private static final String ARG_MESSAGE = "message"; 877 | 878 | public static ErrorDialog newInstance(String message) { 879 | ErrorDialog dialog = new ErrorDialog(); 880 | Bundle args = new Bundle(); 881 | args.putString(ARG_MESSAGE, message); 882 | dialog.setArguments(args); 883 | return dialog; 884 | } 885 | 886 | @NonNull 887 | @Override 888 | public Dialog onCreateDialog(Bundle savedInstanceState) { 889 | final Activity activity = getActivity(); 890 | return new AlertDialog.Builder(activity) 891 | .setMessage(getArguments().getString(ARG_MESSAGE)) 892 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 893 | @Override 894 | public void onClick(DialogInterface dialogInterface, int i) { 895 | activity.finish(); 896 | } 897 | }).create(); 898 | } 899 | 900 | } 901 | 902 | /** 903 | * Shows OK/Cancel confirmation dialog about camera permission. 904 | */ 905 | public static class ConfirmationDialog extends DialogFragment { 906 | 907 | @NonNull 908 | @Override 909 | public Dialog onCreateDialog(Bundle savedInstanceState) { 910 | final Fragment parent = getParentFragment(); 911 | return new AlertDialog.Builder(getActivity()) 912 | .setMessage(R.string.request_permission) 913 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 914 | @Override 915 | public void onClick(DialogInterface dialog, int which) { 916 | parent.requestPermissions(new String[]{Manifest.permission.CAMERA}, 917 | REQUEST_PERMISSIONS); 918 | } 919 | }) 920 | .setNegativeButton(android.R.string.cancel, 921 | new DialogInterface.OnClickListener() { 922 | @Override 923 | public void onClick(DialogInterface dialog, int which) { 924 | Activity activity = parent.getActivity(); 925 | if (activity != null) { 926 | activity.finish(); 927 | } 928 | } 929 | }) 930 | .create(); 931 | } 932 | } 933 | 934 | 935 | public void shareBitmap(Bitmap bitmap) { 936 | 937 | Intent i = new Intent(Intent.ACTION_SEND); 938 | 939 | i.setType("image/jpg"); 940 | 941 | i.putExtra(Intent.EXTRA_STREAM, getImageUri(getActivity(), bitmap)); 942 | try { 943 | startActivity(Intent.createChooser(i, getString(R.string.share))); 944 | } catch (android.content.ActivityNotFoundException ex) { 945 | // no app installed to share... should never happen. 946 | } 947 | 948 | 949 | } 950 | 951 | public Uri getImageUri(Context inContext, Bitmap inImage) { 952 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 953 | inImage.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, bytes); 954 | 955 | String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, PseudoUUID.create(), null); 956 | return Uri.parse(path); 957 | } 958 | } 959 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/OnFrameAvailableListener.java: -------------------------------------------------------------------------------- 1 | package de.uwepost.android.deltacam; 2 | 3 | public interface OnFrameAvailableListener { 4 | void onFrameArrayInt(int[] buffer); 5 | } -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/PseudoUUID.java: -------------------------------------------------------------------------------- 1 | package de.uwepost.android.deltacam; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by uwe on 15.03.18. 7 | */ 8 | 9 | public class PseudoUUID { 10 | 11 | private static final int LENGTH = 10; 12 | private static Random rnd = new Random(); 13 | 14 | public static String create() { 15 | String res=""; 16 | for(int i=0; i0; 104 | } 105 | 106 | public boolean getLighten() { 107 | return mScriptC.get_lighten()>0; 108 | } 109 | 110 | public void setDarken() { 111 | mScriptC.set_darken((short)1); 112 | mScriptC.set_lighten((short)0); 113 | } 114 | public void setLighten() { 115 | mScriptC.set_darken((short)0); 116 | mScriptC.set_lighten((short)1); 117 | } 118 | 119 | @Override 120 | public void onBufferAvailable(Allocation a) { 121 | // Get the new frame into the input allocation 122 | mInputAllocation.ioReceive(); 123 | 124 | if(reset) { firstRun=true; reset=false;} 125 | 126 | // Run processing pass if we should send a frame 127 | final long current = System.currentTimeMillis(); 128 | if ((current - mLastProcessed) >= mFrameEveryMs) { 129 | mScriptC.set_firstRun(firstRun?(short)1:0); 130 | mScriptC.forEach_yuv2rgbFrames(mScriptAllocation, mOutputAllocation); 131 | if (mFrameCallback != null) { 132 | mOutputAllocationInt.copyTo(mOutBufferInt); 133 | mFrameCallback.onFrameArrayInt(mOutBufferInt); 134 | } 135 | mLastProcessed = current; 136 | firstRun=false; 137 | } 138 | } 139 | 140 | public void reset() { 141 | reset=true; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Application/src/main/java/de/uwepost/android/deltacam/VerticalSeekBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Uwe Post 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.uwepost.android.deltacam; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.support.v7.widget.AppCompatSeekBar; 21 | import android.util.AttributeSet; 22 | import android.view.MotionEvent; 23 | 24 | 25 | public class VerticalSeekBar extends AppCompatSeekBar { 26 | 27 | protected OnSeekBarChangeListener changeListener; 28 | protected int x, y, z, w; 29 | 30 | public VerticalSeekBar(Context context) { 31 | super(context); 32 | } 33 | 34 | public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) { 35 | super(context, attrs, defStyle); 36 | } 37 | 38 | public VerticalSeekBar(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | } 41 | 42 | @Override 43 | protected synchronized void onSizeChanged(int w, int h, int oldw, int oldh) { 44 | super.onSizeChanged(h, w, oldh, oldw); 45 | 46 | this.x = w; 47 | this.y = h; 48 | this.z = oldw; 49 | this.w = oldh; 50 | } 51 | 52 | @Override 53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 54 | super.onMeasure(heightMeasureSpec, widthMeasureSpec); 55 | setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); 56 | } 57 | 58 | @Override 59 | protected void onDraw(Canvas c) { 60 | c.rotate(-90); 61 | c.translate(-getHeight(), 0); 62 | 63 | super.onDraw(c); 64 | } 65 | 66 | @Override 67 | public boolean onTouchEvent(MotionEvent event) { 68 | if (!isEnabled()) { 69 | return false; 70 | } 71 | 72 | switch (event.getAction()) { 73 | case MotionEvent.ACTION_DOWN: 74 | setSelected(true); 75 | setPressed(true); 76 | if (changeListener != null) changeListener.onStartTrackingTouch(this); 77 | break; 78 | case MotionEvent.ACTION_UP: 79 | setSelected(false); 80 | setPressed(false); 81 | if (changeListener != null) changeListener.onStopTrackingTouch(this); 82 | break; 83 | case MotionEvent.ACTION_MOVE: 84 | int progress = getMax() - (int) (getMax() * event.getY() / getHeight()); 85 | setProgress(progress); 86 | onSizeChanged(getWidth(), getHeight(), 0, 0); 87 | if (changeListener != null) changeListener.onProgressChanged(this, progress, true); 88 | break; 89 | 90 | case MotionEvent.ACTION_CANCEL: 91 | break; 92 | } 93 | return true; 94 | } 95 | 96 | @Override 97 | public synchronized void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) { 98 | changeListener = listener; 99 | } 100 | 101 | @Override 102 | public synchronized void setProgress(int progress) { 103 | if (progress >= 0) 104 | super.setProgress(progress); 105 | 106 | else 107 | super.setProgress(0); 108 | onSizeChanged(x, y, z, w); 109 | if (changeListener != null) changeListener.onProgressChanged(this, progress, false); 110 | } 111 | } -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/darken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-hdpi/darken.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-hdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-hdpi/lighten.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/tile.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-hdpi/tile.9.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/darken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-mdpi/darken.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-mdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-mdpi/lighten.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/slider_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-mdpi/slider_bg.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/darken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xhdpi/darken.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xhdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xhdpi/lighten.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/darken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xxhdpi/darken.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xxhdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/Application/src/main/res/drawable-xxhdpi/lighten.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable/seekbar_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Application/src/main/res/drawable/seekbar_thumb_grey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Application/src/main/res/layout/activity_camera.xml: -------------------------------------------------------------------------------- 1 | 16 | 23 | -------------------------------------------------------------------------------- /Application/src/main/res/layout/fragment_camera2_basic.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 20 | 24 | 25 | 26 | 32 | 33 | 40 | 41 | 46 | 47 | 48 | 51 | 59 | 60 | 70 | 71 | 72 | 73 | 78 | 79 | 87 | 88 | 91 | 100 | 109 | 110 | 111 | 112 | 121 | 122 | 123 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /Application/src/main/res/values-sw600dp/template-dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | @dimen/margin_huge 22 | @dimen/margin_medium 23 | 24 | 25 | -------------------------------------------------------------------------------- /Application/src/main/res/values-sw600dp/template-styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Application/src/main/res/values-v11/template-styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Application/src/main/res/values/base-strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | DeltaCamera 20 | 21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Application/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #cc4285f4 19 | #ffdd55 20 | #999999 21 | #000000 22 | #ffffff 23 | 24 | -------------------------------------------------------------------------------- /Application/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | Picture 18 | Info 19 | This sample needs camera permission. 20 | This device doesn\'t support Camera2 API. 21 | share 22 | 23 | -------------------------------------------------------------------------------- /Application/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /Application/src/main/res/values/template-dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 4dp 22 | 8dp 23 | 16dp 24 | 32dp 25 | 64dp 26 | 27 | 28 | 29 | @dimen/margin_medium 30 | @dimen/margin_medium 31 | 32 | 33 | -------------------------------------------------------------------------------- /Application/src/main/res/values/template-styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 34 | 35 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Application/src/main/rs/yuv2rgb.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Uwe Post 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma version(1) 17 | #pragma rs java_package_name(de.uwepost.yuv2rgb) 18 | #pragma rs_fp_relaxed 19 | 20 | rs_allocation gCurrentFrame; 21 | rs_allocation gIntFrame; 22 | rs_allocation gFirstFrame; 23 | 24 | uint8_t firstRun = 0; 25 | 26 | uint8_t threshold = 50; 27 | 28 | uint8_t lighten=1; 29 | uint8_t darken=0; 30 | 31 | 32 | 33 | uchar4 __attribute__((kernel)) yuv2rgbFrames(uchar4 prevPixel,uint32_t x,uint32_t y) 34 | { 35 | 36 | // Read in pixel values from latest frame - YUV color space 37 | // The functions rsGetElementAtYuv_uchar_? require API 18 38 | uchar4 curPixel; 39 | curPixel.r = rsGetElementAtYuv_uchar_Y(gCurrentFrame, x, y); 40 | curPixel.g = rsGetElementAtYuv_uchar_U(gCurrentFrame, x, y); 41 | curPixel.b = rsGetElementAtYuv_uchar_V(gCurrentFrame, x, y); 42 | 43 | // This function uses the NTSC formulae to convert YUV to RBG 44 | uchar4 out = rsYuvToRGBA_uchar4(curPixel.r, curPixel.g, curPixel.b); 45 | 46 | // TEST CODE 47 | // calc avg 48 | // int avg = (out.b+out.r+out.g)/3; 49 | // remove colors if red is not dominant 50 | // if(out.r < (avg+avg/4)) { 51 | // out.g = out.b = out.r = avg; 52 | // } 53 | 54 | if(firstRun) { 55 | rsSetElementAt_int(gFirstFrame, 0xff000000 | out.r << 16 | out.g << 8 | out.b, x, y); 56 | rsSetElementAt_int(gIntFrame, 0xff000000 | out.r << 16 | out.g << 8 | out.b, x, y); 57 | } else { 58 | // get pixel from first frame 59 | uint32_t first = rsGetElementAt_int(gFirstFrame,x,y); 60 | uchar4 firstPixel; 61 | firstPixel.r = (first>>16)&0xff; 62 | firstPixel.g = (first>>8)&0xff; 63 | firstPixel.b = (first)&0xff; 64 | 65 | // get pixel from last frame 66 | uint32_t last = rsGetElementAt_int(gIntFrame,x,y); 67 | uchar4 lastPixel; 68 | lastPixel.r = (last>>16)&0xff; 69 | lastPixel.g = (last>>8)&0xff; 70 | lastPixel.b = (last)&0xff; 71 | 72 | uint8_t dirty=0; 73 | if(darken) { 74 | if(firstPixel.r-out.r>threshold) { dirty=1;} 75 | if(firstPixel.g-out.g>threshold) { dirty=1;} 76 | if(firstPixel.b-out.b>threshold) { dirty=1;} 77 | if(dirty) { 78 | // if has been darker... 79 | if(out.r>lastPixel.r || out.g>lastPixel.g || out.b>lastPixel.b) { 80 | // do not overwrite. 81 | dirty=0; 82 | } 83 | } 84 | } else if(lighten) { 85 | if(out.r-firstPixel.r>threshold) { dirty=1; } 86 | if(out.g-firstPixel.g>threshold) { dirty=1; } 87 | if(out.b-firstPixel.b>threshold) { dirty=1; } 88 | if(dirty) { 89 | // if has been lighter ... 90 | if(out.r 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | -------------- 3 | 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright {yyyy} {name of copyright owner} 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | DeltaCamera 3 | =========== 4 | 5 | This app uses the camera to record movement (thus "delta") and visualize it in one picture. 6 | For example running ants leave dark lines on a light surface, or driving cars or a torch make 7 | light lines on a dark surface. Make sure not to move the camera during recording or use a tripod. 8 | You can share images, adjust sensitivity and switch between light-on-dark and dark-on-light mode. 9 | 10 | [Get it on F-Droid](https://f-droid.org/packages/de.uwepost.android.deltacam/) 13 | 14 | Example images 15 | -------------- 16 | 17 | Ants (dark on light): 18 | 19 | ![Ants](https://github.com/upost/DeltaCamera/blob/master/screenshots/ants1.jpg) 20 | 21 | ![More Ants](https://github.com/upost/DeltaCamera/blob/master/screenshots/ants2.jpg) 22 | 23 | A small fountain (light on dark): 24 | 25 | ![Fountain](https://github.com/upost/DeltaCamera/blob/master/screenshots/fountain.jpg) 26 | 27 | 28 | 29 | Based on Android's Camera2 API sample, using RenderScript. Actually this is a how-to-use-renderscript 30 | demo. 31 | 32 | 33 | 34 | License 35 | ------- 36 | 37 | Copyright 2018 Uwe Post 38 | 39 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 40 | license agreements. See the NOTICE file distributed with this work for 41 | additional information regarding copyright ownership. The ASF licenses this 42 | file to you under the Apache License, Version 2.0 (the "License"); you may not 43 | use this file except in compliance with the License. You may obtain a copy of 44 | the License at 45 | 46 | http://www.apache.org/licenses/LICENSE-2.0 47 | 48 | Unless required by applicable law or agreed to in writing, software 49 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 50 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 51 | License for the specific language governing permissions and limitations under 52 | the License. 53 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gfx/deltacam-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gfx/deltacam-icon-1024.png -------------------------------------------------------------------------------- /gfx/deltacam-icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gfx/deltacam-icon-512.png -------------------------------------------------------------------------------- /gfx/deltacam-icon-large.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gfx/deltacam-icon-large.xcf -------------------------------------------------------------------------------- /gfx/funktiongrafik1024x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gfx/funktiongrafik1024x500.jpg -------------------------------------------------------------------------------- /gfx/funktiongrafik1024x500.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gfx/funktiongrafik1024x500.xcf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 30 12:17:22 CET 2018 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-4.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /screenshots/ants1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/screenshots/ants1.jpg -------------------------------------------------------------------------------- /screenshots/ants2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/screenshots/ants2.jpg -------------------------------------------------------------------------------- /screenshots/fountain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/screenshots/fountain.jpg -------------------------------------------------------------------------------- /screenshots/lightpaint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upost/DeltaCamera/b8603a3df2db5c63a613f81f6a2b2725db3bae9e/screenshots/lightpaint.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'Application' 2 | --------------------------------------------------------------------------------