securesmartcam / app / src / main / java / org / witness / obscuracam / ui / ImageEditor.java @ 41590feb
History | View | Annotate | Download (55.5 KB)
1 |
package org.witness.obscuracam.ui; |
---|---|
2 |
|
3 |
import android.Manifest; |
4 |
import android.app.Activity; |
5 |
import android.app.AlertDialog; |
6 |
import android.app.ProgressDialog; |
7 |
import android.content.ContentUris; |
8 |
import android.content.Context; |
9 |
import android.content.DialogInterface; |
10 |
import android.content.Intent; |
11 |
import android.content.pm.PackageManager; |
12 |
import android.content.res.Configuration; |
13 |
import android.content.res.Resources; |
14 |
import android.database.Cursor; |
15 |
import android.graphics.Bitmap; |
16 |
import android.graphics.Bitmap.CompressFormat; |
17 |
import android.graphics.BitmapFactory; |
18 |
import android.graphics.Canvas; |
19 |
import android.graphics.Color; |
20 |
import android.graphics.ColorMatrix; |
21 |
import android.graphics.ColorMatrixColorFilter; |
22 |
import android.graphics.Matrix; |
23 |
import android.graphics.Paint; |
24 |
import android.graphics.Paint.Style; |
25 |
import android.graphics.PixelFormat; |
26 |
import android.graphics.PointF; |
27 |
import android.graphics.RectF; |
28 |
import android.media.ExifInterface; |
29 |
import android.net.Uri; |
30 |
import android.os.Build; |
31 |
import android.os.Bundle; |
32 |
import android.os.Environment; |
33 |
import android.os.Handler; |
34 |
import android.os.Message; |
35 |
import android.os.Vibrator; |
36 |
import android.provider.MediaStore; |
37 |
import android.support.design.widget.Snackbar; |
38 |
import android.support.v4.app.ActivityCompat; |
39 |
import android.support.v4.content.ContextCompat; |
40 |
import android.support.v7.app.AppCompatActivity; |
41 |
import android.support.v7.widget.LinearLayoutManager; |
42 |
import android.support.v7.widget.RecyclerView; |
43 |
import android.util.Log; |
44 |
import android.util.TypedValue; |
45 |
import android.view.Display; |
46 |
import android.view.Menu; |
47 |
import android.view.MenuInflater; |
48 |
import android.view.MenuItem; |
49 |
import android.view.MotionEvent; |
50 |
import android.view.View; |
51 |
import android.view.View.OnClickListener; |
52 |
import android.view.View.OnTouchListener; |
53 |
import android.view.ViewGroup; |
54 |
import android.view.Window; |
55 |
import android.widget.Button; |
56 |
import android.widget.ImageView; |
57 |
import android.widget.Toast; |
58 |
|
59 |
import org.witness.obscuracam.ui.adapters.ImageRegionOptionsRecyclerViewAdapter; |
60 |
import org.witness.obscuracam.photo.detect.AndroidFaceDetection; |
61 |
import org.witness.obscuracam.photo.detect.DetectedFace; |
62 |
import org.witness.obscuracam.photo.detect.FaceDetection; |
63 |
import org.witness.obscuracam.photo.filters.MaskObscure; |
64 |
import org.witness.obscuracam.photo.filters.RegionProcesser; |
65 |
import org.witness.obscuracam.photo.jpegredaction.JpegRedaction; |
66 |
import org.witness.obscuracam.ObscuraApp; |
67 |
import org.witness.sscphase1.R; |
68 |
|
69 |
import java.io.File; |
70 |
import java.io.FileInputStream; |
71 |
import java.io.FileNotFoundException; |
72 |
import java.io.FileOutputStream; |
73 |
import java.io.IOException; |
74 |
import java.io.InputStream; |
75 |
import java.io.OutputStream; |
76 |
import java.nio.channels.FileChannel; |
77 |
import java.text.SimpleDateFormat; |
78 |
import java.util.ArrayList; |
79 |
import java.util.Date; |
80 |
import java.util.HashMap; |
81 |
import java.util.Iterator; |
82 |
import java.util.Map; |
83 |
import java.util.Properties; |
84 |
|
85 |
public class ImageEditor extends AppCompatActivity implements OnTouchListener, OnClickListener, ImageRegionOptionsRecyclerViewAdapter.ImageRegionOptionsRecyclerViewAdapterListener { |
86 |
|
87 |
private static final int WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST = 1; |
88 |
|
89 |
public final static String MIME_TYPE_JPEG = "image/jpeg"; |
90 |
|
91 |
// Colors for region squares
|
92 |
|
93 |
public final static int DRAW_COLOR = 0x00000000; |
94 |
public final static int DETECTED_COLOR = 0x00000000; |
95 |
public final static int OBSCURED_COLOR = 0x00000000; |
96 |
|
97 |
// Constants for the menu items, currently these are in an XML file (menu/image_editor_menu.xml, strings.xml)
|
98 |
public final static int ABOUT_MENU_ITEM = 0; |
99 |
public final static int DELETE_ORIGINAL_MENU_ITEM = 1; |
100 |
public final static int SAVE_MENU_ITEM = 2; |
101 |
public final static int SHARE_MENU_ITEM = 3; |
102 |
public final static int NEW_REGION_MENU_ITEM = 4; |
103 |
|
104 |
// Selection sizes in DP
|
105 |
public final static int SELECTION_BORDER_WIDTH = 5; |
106 |
public final static int SELECTION_HANDLE_RADIUS = 10; |
107 |
public final static int SELECTION_HANDLE_TOUCH_RADIUS = 15; |
108 |
|
109 |
// Constants for Informa
|
110 |
public final static int FROM_INFORMA = 100; |
111 |
public final static String LOG = "[Image Editor ********************]"; |
112 |
|
113 |
// Image Matrix
|
114 |
Matrix matrix = new Matrix();
|
115 |
Matrix matrix_inverted = new Matrix();
|
116 |
|
117 |
// Saved Matrix for not allowing a current operation (over max zoom)
|
118 |
Matrix savedMatrix = new Matrix();
|
119 |
|
120 |
// We can be in one of these 3 states
|
121 |
static final int NONE = 0; |
122 |
static final int DRAG = 1; |
123 |
static final int ZOOM = 2; |
124 |
static final int TAP = 3; |
125 |
int mode = NONE;
|
126 |
|
127 |
|
128 |
// Maximum zoom scale
|
129 |
static final float MAX_SCALE = 10f; |
130 |
|
131 |
// Constant for autodetection dialog
|
132 |
static final int DIALOG_DO_AUTODETECTION = 0; |
133 |
|
134 |
// For Zooming
|
135 |
float startFingerSpacing = 0f; |
136 |
float endFingerSpacing = 0f; |
137 |
PointF startFingerSpacingMidPoint = new PointF();
|
138 |
|
139 |
// For Dragging
|
140 |
PointF startPoint = new PointF();
|
141 |
|
142 |
// Don't allow it to move until the finger moves more than this amount
|
143 |
// Later in the code, the minMoveDistance in real pixels is calculated
|
144 |
// to account for different touch screen resolutions
|
145 |
float minMoveDistanceDP = 5f; |
146 |
float minMoveDistance; // = ViewConfiguration.get(this).getScaledTouchSlop(); |
147 |
|
148 |
// zoom in and zoom out buttons
|
149 |
Button zoomIn, zoomOut, btnSave, btnShare, btnPreview, btnNew;
|
150 |
|
151 |
// ImageView for the original (scaled) image
|
152 |
ImageView imageView;
|
153 |
|
154 |
|
155 |
// Bitmap for the original image (scaled)
|
156 |
Bitmap imageBitmap; |
157 |
|
158 |
// Bitmap for holding the realtime obscured image
|
159 |
Bitmap obscuredBmp; |
160 |
|
161 |
// Canvas for drawing the realtime obscuring
|
162 |
Canvas obscuredCanvas;
|
163 |
|
164 |
// Paint obscured
|
165 |
Paint obscuredPaint;
|
166 |
|
167 |
//bitmaps for corners
|
168 |
/*
|
169 |
private final static float CORNER_SIZE = 26;
|
170 |
|
171 |
Bitmap bitmapCornerUL;
|
172 |
Bitmap bitmapCornerUR;
|
173 |
Bitmap bitmapCornerLL;
|
174 |
Bitmap bitmapCornerLR;*/
|
175 |
|
176 |
|
177 |
// Vector to hold ImageRegions
|
178 |
ArrayList<ImageRegion> imageRegions = new ArrayList<ImageRegion>(); |
179 |
|
180 |
// The original image dimensions (not scaled)
|
181 |
int originalImageWidth;
|
182 |
int originalImageHeight;
|
183 |
|
184 |
// So we can give some haptic feedback to the user
|
185 |
Vibrator vibe; |
186 |
|
187 |
// Original Image Uri
|
188 |
Uri originalImageUri; |
189 |
|
190 |
// sample sized used to downsize from native photo
|
191 |
int inSampleSize;
|
192 |
|
193 |
// Saved Image Uri
|
194 |
Uri savedImageUri; |
195 |
|
196 |
// Constant for temp filename
|
197 |
public final static String TMP_FILE_NAME = "tmp.jpg"; |
198 |
|
199 |
public final static String TMP_FILE_DIRECTORY = "/Android/data/org.witness.sscphase1/files/"; |
200 |
|
201 |
|
202 |
//handles threaded events for the UI thread
|
203 |
private Handler mHandler = new Handler() { |
204 |
|
205 |
@Override
|
206 |
public void handleMessage(Message msg) { |
207 |
super.handleMessage(msg);
|
208 |
|
209 |
|
210 |
switch (msg.what) {
|
211 |
|
212 |
case 3: //completed |
213 |
mProgressDialog.dismiss(); |
214 |
|
215 |
//Toast autodetectedToast = Toast.makeText(ImageEditor.this, result + " face(s) detected", Toast.LENGTH_SHORT);
|
216 |
//autodetectedToast.show();
|
217 |
|
218 |
break;
|
219 |
default:
|
220 |
super.handleMessage(msg);
|
221 |
} |
222 |
} |
223 |
|
224 |
}; |
225 |
|
226 |
//UI for background threads
|
227 |
ProgressDialog mProgressDialog; |
228 |
|
229 |
// Handles when we should do realtime preview and when we shouldn't
|
230 |
private boolean doRealtimePreview = true; |
231 |
private boolean needsUpdate = true; |
232 |
|
233 |
// Keep track of the orientation
|
234 |
private int originalImageOrientation = ExifInterface.ORIENTATION_NORMAL; |
235 |
|
236 |
// for saving images
|
237 |
private final static String EXPORT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; |
238 |
|
239 |
private View imageViewOverlay; |
240 |
private RecyclerView recyclerViewRegionOptions;
|
241 |
|
242 |
/*
|
243 |
private class mAutoDetectTask extends AsyncTask<Integer, Integer, Long> {
|
244 |
protected Long doInBackground(Integer... params) {
|
245 |
return (long)doAutoDetection();
|
246 |
}
|
247 |
|
248 |
protected void onProgressUpdate(Integer... progress) {
|
249 |
|
250 |
}
|
251 |
|
252 |
protected void onPostExecute(Long result) {
|
253 |
|
254 |
mProgressDialog.dismiss();
|
255 |
|
256 |
Toast autodetectedToast = Toast.makeText(ImageEditor.this, result + " face(s) detected", Toast.LENGTH_SHORT);
|
257 |
autodetectedToast.show();
|
258 |
}
|
259 |
}*/
|
260 |
|
261 |
|
262 |
@SuppressWarnings("unused") |
263 |
@Override
|
264 |
public void onCreate(Bundle savedInstanceState) { |
265 |
super.onCreate(savedInstanceState);
|
266 |
|
267 |
getSupportActionBar().setTitle("");
|
268 |
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
269 |
|
270 |
matrix.invert(matrix_inverted); |
271 |
|
272 |
String versNum = ""; |
273 |
|
274 |
try {
|
275 |
String pkg = getPackageName();
|
276 |
versNum = getPackageManager().getPackageInfo(pkg, 0).versionName;
|
277 |
} catch (Exception e) { |
278 |
versNum = "";
|
279 |
} |
280 |
|
281 |
setTitle(getString(R.string.app_name) + " (" + versNum + ")"); |
282 |
|
283 |
// requestWindowFeature(Window.FEATURE_NO_TITLE);
|
284 |
setContentView(R.layout.imageviewer); |
285 |
|
286 |
// Calculate the minimum distance
|
287 |
minMoveDistance = minMoveDistanceDP * this.getResources().getDisplayMetrics().density + 0.5f; |
288 |
|
289 |
// The ImageView that contains the image we are working with
|
290 |
imageView = (ImageView) findViewById(R.id.ImageEditorImageView);
|
291 |
imageViewOverlay = new RegionOverlayView(this); |
292 |
imageViewOverlay.setBackgroundColor(0x01000000);
|
293 |
((ViewGroup) imageView.getParent()).addView(imageViewOverlay); |
294 |
imageView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { |
295 |
@Override
|
296 |
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { |
297 |
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) imageViewOverlay.getLayoutParams(); |
298 |
params.leftMargin = left; |
299 |
params.topMargin = top; |
300 |
params.width = (right - left); |
301 |
params.height = (bottom - top); |
302 |
imageViewOverlay.setLayoutParams(params); |
303 |
} |
304 |
}); |
305 |
|
306 |
recyclerViewRegionOptions = (RecyclerView) findViewById(R.id.recycler_view_region_options); |
307 |
recyclerViewRegionOptions.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); |
308 |
ImageRegionOptionsRecyclerViewAdapter adapter = new ImageRegionOptionsRecyclerViewAdapter(this); |
309 |
adapter.setListener(this);
|
310 |
recyclerViewRegionOptions.setAdapter(adapter); |
311 |
recyclerViewRegionOptions.bringToFront(); |
312 |
recyclerViewRegionOptions.setVisibility(View.GONE);
|
313 |
|
314 |
// Buttons for zooming
|
315 |
zoomIn = (Button) this.findViewById(R.id.ZoomIn); |
316 |
zoomOut = (Button) this.findViewById(R.id.ZoomOut); |
317 |
zoomIn.setOnClickListener(this);
|
318 |
zoomOut.setOnClickListener(this);
|
319 |
|
320 |
/*
|
321 |
btnNew = (Button) this.findViewById(R.id.New);
|
322 |
btnSave = (Button) this.findViewById(R.id.Save);
|
323 |
btnShare = (Button) this.findViewById(R.id.Share);
|
324 |
btnPreview = (Button) this.findViewById(R.id.Preview);
|
325 |
|
326 |
// this, ImageEditor will be the onClickListener for the buttons
|
327 |
|
328 |
btnNew.setOnClickListener(this);
|
329 |
btnSave.setOnClickListener(this);
|
330 |
btnShare.setOnClickListener(this);
|
331 |
btnPreview.setOnClickListener(this);
|
332 |
*/
|
333 |
|
334 |
// Instantiate the vibrator
|
335 |
vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
336 |
|
337 |
// Passed in from CameraObscuraMainMenu
|
338 |
originalImageUri = getIntent().getData(); |
339 |
|
340 |
// If originalImageUri is null, we are likely coming from another app via "share"
|
341 |
if (originalImageUri == null) { |
342 |
if (getIntent().hasExtra(Intent.EXTRA_STREAM)) {
|
343 |
originalImageUri = (Uri) getIntent().getExtras().get(Intent.EXTRA_STREAM); |
344 |
} else if (getIntent().hasExtra("bitmap")) { |
345 |
Bitmap b = (Bitmap) getIntent().getExtras().get("bitmap");
|
346 |
setBitmap(b); |
347 |
|
348 |
boolean autodetect = true; |
349 |
|
350 |
if (autodetect) {
|
351 |
|
352 |
mProgressDialog = ProgressDialog.show(this, "", "Detecting faces...", true, true); |
353 |
|
354 |
doAutoDetectionThread(); |
355 |
|
356 |
|
357 |
} |
358 |
|
359 |
originalImageWidth = b.getWidth(); |
360 |
originalImageHeight = b.getHeight(); |
361 |
return;
|
362 |
|
363 |
} |
364 |
} |
365 |
|
366 |
|
367 |
// Load the image if it isn't null
|
368 |
if (originalImageUri != null) { |
369 |
|
370 |
// try {
|
371 |
// InputStream is;
|
372 |
// if (originalImageUri.getScheme() != null && originalImageUri.getScheme().contentEquals("content"))
|
373 |
// is = getContentResolver().openInputStream(originalImageUri);
|
374 |
// else
|
375 |
// is = new FileInputStream(new File(originalImageUri.toString()));
|
376 |
// if (is != null) {
|
377 |
// // Get orientation of image
|
378 |
// try {
|
379 |
// ExifInterface ei = new ExifInterface(is);
|
380 |
// originalImageOrientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
381 |
// debug(ObscuraApp.TAG,"Orientation: " + originalImageOrientation);
|
382 |
// } catch (IOException e1) {
|
383 |
// debug(ObscuraApp.TAG,"Couldn't get Orientation");
|
384 |
// e1.printStackTrace();
|
385 |
// }
|
386 |
// is.close();
|
387 |
// }
|
388 |
// } catch (Exception e) {
|
389 |
// e.printStackTrace();
|
390 |
// }
|
391 |
// }
|
392 |
|
393 |
|
394 |
// Get the orientation
|
395 |
File originalFilename = pullPathFromUri(originalImageUri);
|
396 |
try {
|
397 |
ExifInterface ei = new ExifInterface(originalFilename.getAbsolutePath());
|
398 |
originalImageOrientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); |
399 |
debug(ObscuraApp.TAG, "Orientation: " + originalImageOrientation);
|
400 |
} catch (IOException e1) { |
401 |
debug(ObscuraApp.TAG, "Couldn't get Orientation");
|
402 |
e1.printStackTrace(); |
403 |
} |
404 |
|
405 |
//debug(ObscuraApp.TAG,"loading uri: " + pullPathFromUri(originalImageUri));
|
406 |
|
407 |
// Load up smaller image
|
408 |
try {
|
409 |
// Load up the image's dimensions not the image itself
|
410 |
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
|
411 |
bmpFactoryOptions.inJustDecodeBounds = true;
|
412 |
// Needs to be this config for Google Face Detection
|
413 |
bmpFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565; |
414 |
// Parse the image
|
415 |
InputStream inputStream = streamFromUri(originalImageUri);
|
416 |
Bitmap loadedBitmap = BitmapFactory.decodeStream(inputStream, null, bmpFactoryOptions);
|
417 |
inputStream.close(); |
418 |
|
419 |
// Hold onto the unscaled dimensions
|
420 |
originalImageWidth = bmpFactoryOptions.outWidth; |
421 |
originalImageHeight = bmpFactoryOptions.outHeight; |
422 |
// If it is rotated, transpose the width and height
|
423 |
// Should probably look to see if there are different rotation constants being used
|
424 |
if (originalImageOrientation == ExifInterface.ORIENTATION_ROTATE_90
|
425 |
|| originalImageOrientation == ExifInterface.ORIENTATION_ROTATE_270) { |
426 |
int tmpWidth = originalImageWidth;
|
427 |
originalImageWidth = originalImageHeight; |
428 |
originalImageHeight = tmpWidth; |
429 |
} |
430 |
|
431 |
// Get the current display to calculate ratios
|
432 |
Display currentDisplay = getWindowManager().getDefaultDisplay(); |
433 |
|
434 |
// Ratios between the display and the image
|
435 |
/**
|
436 |
double widthRatio = Math.floor(bmpFactoryOptions.outWidth / currentDisplay.getWidth());
|
437 |
double heightRatio = Math.floor(bmpFactoryOptions.outHeight / currentDisplay.getHeight());
|
438 |
|
439 |
// If both of the ratios are greater than 1,
|
440 |
// one of the sides of the image is greater than the screen
|
441 |
if (heightRatio > widthRatio) {
|
442 |
// Height ratio is larger, scale according to it
|
443 |
inSampleSize = (int) heightRatio;
|
444 |
} else {
|
445 |
// Width ratio is larger, scale according to it
|
446 |
inSampleSize = (int) widthRatio;
|
447 |
}
|
448 |
|
449 |
bmpFactoryOptions.inSampleSize = inSampleSize;
|
450 |
**/
|
451 |
|
452 |
bmpFactoryOptions.inSampleSize = 2;
|
453 |
boolean notLoaded = true; |
454 |
|
455 |
while (notLoaded) {
|
456 |
|
457 |
try {
|
458 |
// Decode it for real
|
459 |
bmpFactoryOptions.inJustDecodeBounds = false;
|
460 |
inputStream = streamFromUri(originalImageUri); |
461 |
loadedBitmap = BitmapFactory.decodeStream(inputStream, null, bmpFactoryOptions);
|
462 |
inputStream.close(); |
463 |
debug(ObscuraApp.TAG, "Was: " + loadedBitmap.getConfig());
|
464 |
notLoaded = false;
|
465 |
} |
466 |
catch (OutOfMemoryError oom) |
467 |
{ |
468 |
bmpFactoryOptions.inSampleSize *= 2;
|
469 |
//try again but smaller!
|
470 |
} |
471 |
} |
472 |
|
473 |
if (loadedBitmap == null) { |
474 |
debug(ObscuraApp.TAG, "bmp is null");
|
475 |
|
476 |
} else {
|
477 |
// Only dealing with 90 and 270 degree rotations, might need to check for others
|
478 |
if (originalImageOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
|
479 |
debug(ObscuraApp.TAG, "Rotating Bitmap 90");
|
480 |
Matrix rotateMatrix = new Matrix();
|
481 |
rotateMatrix.postRotate(90);
|
482 |
loadedBitmap = Bitmap.createBitmap(loadedBitmap, 0, 0, loadedBitmap.getWidth(), loadedBitmap.getHeight(), rotateMatrix, false); |
483 |
} else if (originalImageOrientation == ExifInterface.ORIENTATION_ROTATE_270) { |
484 |
debug(ObscuraApp.TAG, "Rotating Bitmap 270");
|
485 |
Matrix rotateMatrix = new Matrix();
|
486 |
rotateMatrix.postRotate(270);
|
487 |
loadedBitmap = Bitmap.createBitmap(loadedBitmap, 0, 0, loadedBitmap.getWidth(), loadedBitmap.getHeight(), rotateMatrix, false); |
488 |
} |
489 |
|
490 |
setBitmap(loadedBitmap); |
491 |
|
492 |
boolean autodetect = true; |
493 |
|
494 |
if (autodetect) {
|
495 |
// Do auto detect popup
|
496 |
|
497 |
mProgressDialog = ProgressDialog.show(this, "", "Detecting faces...", true, true); |
498 |
|
499 |
doAutoDetectionThread(); |
500 |
} |
501 |
} |
502 |
} catch (Exception e) { |
503 |
Log.e(ObscuraApp.TAG, "error loading bitmap from Uri: " + e.getMessage(), e);
|
504 |
} |
505 |
|
506 |
|
507 |
} |
508 |
|
509 |
/*
|
510 |
bitmapCornerUL = BitmapFactory.decodeResource(getResources(),
|
511 |
R.drawable.edit_region_corner_ul);
|
512 |
bitmapCornerUR = BitmapFactory.decodeResource(getResources(),
|
513 |
R.drawable.edit_region_corner_ur);
|
514 |
bitmapCornerLL = BitmapFactory.decodeResource(getResources(),
|
515 |
R.drawable.edit_region_corner_ll);
|
516 |
bitmapCornerLR = BitmapFactory.decodeResource(getResources(),
|
517 |
R.drawable.edit_region_corner_lr);
|
518 |
*/
|
519 |
} |
520 |
|
521 |
private void setBitmap(Bitmap nBitmap) { |
522 |
imageBitmap = nBitmap; |
523 |
|
524 |
// Get the current display to calculate ratios
|
525 |
Display currentDisplay = getWindowManager().getDefaultDisplay(); |
526 |
|
527 |
float matrixWidthRatio = (float) currentDisplay.getWidth() / (float) imageBitmap.getWidth(); |
528 |
float matrixHeightRatio = (float) currentDisplay.getHeight() / (float) imageBitmap.getHeight(); |
529 |
|
530 |
// Setup the imageView and matrix for scaling
|
531 |
float matrixScale = matrixHeightRatio;
|
532 |
|
533 |
if (matrixWidthRatio < matrixHeightRatio) {
|
534 |
matrixScale = matrixWidthRatio; |
535 |
} |
536 |
|
537 |
// BitmapDrawable d = new BitmapDrawable(getResources(), imageBitmap);
|
538 |
// Drawable d2 = new Drawable() {
|
539 |
// private Paint paint;
|
540 |
//
|
541 |
// @Override
|
542 |
// public void draw(@NonNull Canvas canvas) {
|
543 |
// try {
|
544 |
// if (paint == null) {
|
545 |
// paint = new Paint();
|
546 |
// paint.setStyle(Style.STROKE);
|
547 |
// paint.setStrokeWidth(10f);
|
548 |
// }
|
549 |
// for (ImageRegion currentRegion : imageRegions) {
|
550 |
// RectF regionRect = currentRegion.getBounds();
|
551 |
// if (currentRegion.isSelected())
|
552 |
// paint.setColor(Color.CYAN);
|
553 |
// else
|
554 |
// paint.setColor(Color.YELLOW);
|
555 |
// obscuredCanvas.drawRect(regionRect, paint);
|
556 |
//
|
557 |
// // Draw drag handles
|
558 |
// int handleRadius = 10;
|
559 |
// if (currentRegion.isSelected()) {
|
560 |
// obscuredCanvas.drawCircle(regionRect.centerX(), regionRect.top, handleRadius, paint);
|
561 |
// obscuredCanvas.drawCircle(regionRect.centerX(), regionRect.bottom, handleRadius, paint);
|
562 |
// obscuredCanvas.drawCircle(regionRect.left, regionRect.centerY(), handleRadius, paint);
|
563 |
// obscuredCanvas.drawCircle(regionRect.right, regionRect.centerY(), handleRadius, paint);
|
564 |
// }
|
565 |
// }
|
566 |
// } catch (Exception ignored) {
|
567 |
// }
|
568 |
// }
|
569 |
//
|
570 |
// @Override
|
571 |
// public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
|
572 |
// }
|
573 |
//
|
574 |
// @Override
|
575 |
// public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
576 |
//
|
577 |
// }
|
578 |
//
|
579 |
// @Override
|
580 |
// public int getOpacity() {
|
581 |
// return PixelFormat.TRANSLUCENT;
|
582 |
// }
|
583 |
// };
|
584 |
// d2.setBounds(d.getBounds());
|
585 |
// LayerDrawable ld = new LayerDrawable(new Drawable[] { d, d2 });
|
586 |
// ld.setBounds(d.getBounds());
|
587 |
// imageView.setImageDrawable(ld);
|
588 |
imageView.setImageBitmap(imageBitmap); |
589 |
|
590 |
// Set the OnTouch and OnLongClick listeners to this (ImageEditor)
|
591 |
imageView.setOnTouchListener(this);
|
592 |
imageView.setOnClickListener(this);
|
593 |
imageView.setSoundEffectsEnabled(false);
|
594 |
|
595 |
//PointF midpoint = new PointF((float)imageBitmap.getWidth()/2f, (float)imageBitmap.getHeight()/2f);
|
596 |
matrix.postScale(matrixScale, matrixScale); |
597 |
|
598 |
// This doesn't completely center the image but it get's closer
|
599 |
//int fudge = 42;
|
600 |
matrix.postTranslate((float) ((float) currentDisplay.getWidth() - (float) imageBitmap.getWidth() * (float) matrixScale) / 2f, (float) ((float) currentDisplay.getHeight() - (float) imageBitmap.getHeight() * matrixScale) / 2f); |
601 |
|
602 |
imageView.setImageMatrix(matrix); |
603 |
|
604 |
|
605 |
} |
606 |
|
607 |
public Matrix getMatrix() {
|
608 |
return matrix;
|
609 |
} |
610 |
|
611 |
public Matrix getMatrixInverted() {
|
612 |
return matrix_inverted;
|
613 |
} |
614 |
|
615 |
/*
|
616 |
* Call this to delete the original image, will ask the user
|
617 |
*/
|
618 |
private void showDeleteOriginalDialog() { |
619 |
final AlertDialog.Builder b = new AlertDialog.Builder(this); |
620 |
b.setIcon(android.R.drawable.ic_dialog_alert); |
621 |
b.setTitle(getString(R.string.app_name)); |
622 |
b.setMessage(getString(R.string.confirm_delete)); |
623 |
b.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
624 |
public void onClick(DialogInterface dialog, int whichButton) { |
625 |
|
626 |
try {
|
627 |
// User clicked OK so go ahead and delete
|
628 |
deleteOriginal(); |
629 |
viewImage(savedImageUri); |
630 |
} catch (IOException e) { |
631 |
Log.e(ObscuraApp.TAG, "error saving", e);
|
632 |
} finally {
|
633 |
finish(); |
634 |
} |
635 |
} |
636 |
}); |
637 |
b.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
638 |
public void onClick(DialogInterface dialog, int whichButton) { |
639 |
|
640 |
viewImage(savedImageUri); |
641 |
} |
642 |
}); |
643 |
b.show(); |
644 |
} |
645 |
|
646 |
/*
|
647 |
* Actual deletion of original
|
648 |
*/
|
649 |
private void deleteOriginal() throws IOException { |
650 |
|
651 |
if (originalImageUri != null) { |
652 |
|
653 |
String origFilePath = originalImageUri.getPath();
|
654 |
File fileOrig = new File(origFilePath); |
655 |
|
656 |
if (fileOrig.exists()) {
|
657 |
|
658 |
String[] columnsToSelect = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA}; |
659 |
|
660 |
Uri[] uriBases = {MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.INTERNAL_CONTENT_URI};
|
661 |
|
662 |
for (Uri uriBase : uriBases) {
|
663 |
|
664 |
Cursor imageCursor = getContentResolver().query(uriBase, columnsToSelect, MediaStore.Images.Media.DATA + " = ?", new String[]{origFilePath}, null); |
665 |
//Cursor imageCursor = getContentResolver().query(uriBase, columnsToSelect, MediaStore.Images.Media.DATE_TAKEN + " = ?", new String[] {dateTaken+""}, null );
|
666 |
|
667 |
while (imageCursor.moveToNext()) {
|
668 |
|
669 |
long _id = imageCursor.getLong(imageCursor.getColumnIndex(MediaStore.Images.Media._ID));
|
670 |
|
671 |
getContentResolver().delete(ContentUris.withAppendedId(uriBase, _id), null, null); |
672 |
|
673 |
} |
674 |
} |
675 |
|
676 |
if (fileOrig.exists())
|
677 |
fileOrig.delete(); |
678 |
|
679 |
} else {
|
680 |
getContentResolver().delete(originalImageUri, null, null); |
681 |
} |
682 |
} |
683 |
|
684 |
originalImageUri = null;
|
685 |
} |
686 |
|
687 |
private void doAutoDetectionThread() { |
688 |
Thread thread = new Thread() { |
689 |
public void run() { |
690 |
doAutoDetection(); |
691 |
Message msg = mHandler.obtainMessage(3);
|
692 |
mHandler.sendMessage(msg); |
693 |
} |
694 |
}; |
695 |
thread.start(); |
696 |
} |
697 |
/*
|
698 |
* Do actual auto detection and create regions
|
699 |
*
|
700 |
* public void createImageRegion(int _scaledStartX, int _scaledStartY,
|
701 |
int _scaledEndX, int _scaledEndY,
|
702 |
int _scaledImageWidth, int _scaledImageHeight,
|
703 |
int _imageWidth, int _imageHeight,
|
704 |
int _backgroundColor) {
|
705 |
*/
|
706 |
|
707 |
private int doAutoDetection() { |
708 |
// This should be called via a pop-up/alert mechanism
|
709 |
|
710 |
ArrayList<DetectedFace> dFaces = runFaceDetection();
|
711 |
|
712 |
if (dFaces == null) |
713 |
return 0; |
714 |
|
715 |
// for (int adr = 0; adr < autodetectedRects.length; adr++) {
|
716 |
|
717 |
Iterator<DetectedFace> itDFace = dFaces.iterator();
|
718 |
|
719 |
while (itDFace.hasNext()) {
|
720 |
DetectedFace dFace = itDFace.next(); |
721 |
|
722 |
//debug(ObscuraApp.TAG,"AUTODETECTED imageView Width, Height: " + imageView.getWidth() + " " + imageView.getHeight());
|
723 |
//debug(ObscuraApp.TAG,"UNSCALED RECT:" + autodetectedRects[adr].left + " " + autodetectedRects[adr].top + " " + autodetectedRects[adr].right + " " + autodetectedRects[adr].bottom);
|
724 |
|
725 |
RectF autodetectedRectScaled = new RectF(dFace.bounds.left, dFace.bounds.top, dFace.bounds.right, dFace.bounds.bottom);
|
726 |
|
727 |
//debug(ObscuraApp.TAG,"SCALED RECT:" + autodetectedRectScaled.left + " " + autodetectedRectScaled.top + " " + autodetectedRectScaled.right + " " + autodetectedRectScaled.bottom);
|
728 |
|
729 |
// Probably need to map autodetectedRects to scaled rects
|
730 |
//debug(ObscuraApp.TAG,"MAPPED RECT:" + autodetectedRects[adr].left + " " + autodetectedRects[adr].top + " " + autodetectedRects[adr].right + " " + autodetectedRects[adr].bottom);
|
731 |
|
732 |
float faceBuffer = (autodetectedRectScaled.right - autodetectedRectScaled.left) / 5; |
733 |
|
734 |
boolean isLast = !itDFace.hasNext();
|
735 |
|
736 |
createImageRegion( |
737 |
(autodetectedRectScaled.left - faceBuffer), |
738 |
(autodetectedRectScaled.top - faceBuffer), |
739 |
(autodetectedRectScaled.right + faceBuffer), |
740 |
(autodetectedRectScaled.bottom + faceBuffer), |
741 |
isLast, |
742 |
isLast); |
743 |
} |
744 |
|
745 |
return dFaces.size();
|
746 |
} |
747 |
|
748 |
/*
|
749 |
* The actual face detection calling method
|
750 |
*/
|
751 |
private ArrayList<DetectedFace> runFaceDetection() { |
752 |
ArrayList<DetectedFace> dFaces = new ArrayList<DetectedFace>(); |
753 |
|
754 |
try {
|
755 |
|
756 |
FaceDetection fd = new AndroidFaceDetection(imageBitmap.getWidth(), imageBitmap.getHeight());
|
757 |
int numFaces = fd.findFaces(imageBitmap);
|
758 |
|
759 |
if (numFaces > 0) |
760 |
dFaces.addAll(fd.getFaces(numFaces)); |
761 |
else
|
762 |
{ |
763 |
fd.release(); |
764 |
Bitmap imageGrayScale = toGrayscale(imageBitmap); |
765 |
fd = new AndroidFaceDetection(imageGrayScale.getWidth(), imageGrayScale.getHeight());
|
766 |
numFaces = fd.findFaces(imageGrayScale); |
767 |
if (numFaces > 0) |
768 |
dFaces.addAll(fd.getFaces(numFaces)); |
769 |
} |
770 |
|
771 |
fd.release(); |
772 |
|
773 |
} catch (NullPointerException e) { |
774 |
dFaces = null;
|
775 |
Log.e(getClass().getName(),"error detecting faces",e);
|
776 |
} |
777 |
return dFaces;
|
778 |
} |
779 |
|
780 |
public Bitmap toGrayscale(Bitmap bmpOriginal) {
|
781 |
int width, height;
|
782 |
height = bmpOriginal.getHeight(); |
783 |
width = bmpOriginal.getWidth(); |
784 |
|
785 |
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); |
786 |
Canvas c = new Canvas(bmpGrayscale); |
787 |
Paint paint = new Paint(); |
788 |
ColorMatrix cm = new ColorMatrix();
|
789 |
cm.setSaturation(0);
|
790 |
|
791 |
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
|
792 |
|
793 |
paint.setColorFilter(f); |
794 |
|
795 |
c.drawBitmap(bmpOriginal, 0, 0, paint); |
796 |
|
797 |
|
798 |
return bmpGrayscale;
|
799 |
} |
800 |
|
801 |
public static Bitmap createContrast(Bitmap src, double value) { |
802 |
// image size
|
803 |
int width = src.getWidth();
|
804 |
int height = src.getHeight();
|
805 |
// create output bitmap
|
806 |
Bitmap bmOut = Bitmap.createBitmap(width, height, src.getConfig()); |
807 |
// color information
|
808 |
int A, R, G, B;
|
809 |
int pixel;
|
810 |
// get contrast value
|
811 |
double contrast = Math.pow((100 + value) / 100, 2); |
812 |
|
813 |
// scan through all pixels
|
814 |
for (int x = 0; x < width; ++x) { |
815 |
for (int y = 0; y < height; ++y) { |
816 |
// get pixel color
|
817 |
pixel = src.getPixel(x, y); |
818 |
A = Color.alpha(pixel);
|
819 |
// apply filter contrast for every channel R, G, B
|
820 |
R = Color.red(pixel);
|
821 |
R = (int) (((((R / 255.0) - 0.5) * contrast) + 0.5) * 255.0); |
822 |
if (R < 0) { |
823 |
R = 0;
|
824 |
} else if (R > 255) { |
825 |
R = 255;
|
826 |
} |
827 |
|
828 |
G = Color.red(pixel);
|
829 |
G = (int) (((((G / 255.0) - 0.5) * contrast) + 0.5) * 255.0); |
830 |
if (G < 0) { |
831 |
G = 0;
|
832 |
} else if (G > 255) { |
833 |
G = 255;
|
834 |
} |
835 |
|
836 |
B = Color.red(pixel);
|
837 |
B = (int) (((((B / 255.0) - 0.5) * contrast) + 0.5) * 255.0); |
838 |
if (B < 0) { |
839 |
B = 0;
|
840 |
} else if (B > 255) { |
841 |
B = 255;
|
842 |
} |
843 |
|
844 |
// set new pixel color to output bitmap
|
845 |
bmOut.setPixel(x, y, Color.argb(A, R, G, B));
|
846 |
} |
847 |
} |
848 |
|
849 |
// return final image
|
850 |
return bmOut;
|
851 |
} |
852 |
|
853 |
|
854 |
ImageRegion currRegion = null;
|
855 |
|
856 |
private ImageRegion getCurrentRegion() {
|
857 |
return currRegion;
|
858 |
} |
859 |
|
860 |
private void setCurrentRegion(ImageRegion region) { |
861 |
currRegion = region; |
862 |
for (ImageRegion ir : imageRegions) {
|
863 |
ir.setSelected(ir == region); |
864 |
} |
865 |
// Reorder to the front
|
866 |
if (imageRegions.remove(region)) {
|
867 |
imageRegions.add(region); |
868 |
} |
869 |
if (region != null) { |
870 |
((ImageRegionOptionsRecyclerViewAdapter)recyclerViewRegionOptions.getAdapter()).setCurrentItem(region.mObscureType); |
871 |
recyclerViewRegionOptions.setVisibility(View.VISIBLE);
|
872 |
} else {
|
873 |
recyclerViewRegionOptions.setVisibility(View.GONE);
|
874 |
} |
875 |
} |
876 |
|
877 |
/*
|
878 |
* Handles touches on ImageView
|
879 |
*/
|
880 |
@Override
|
881 |
public boolean onTouch(View v, MotionEvent event) { |
882 |
boolean handled = false; |
883 |
|
884 |
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
885 |
case MotionEvent.ACTION_DOWN:
|
886 |
mode = TAP; |
887 |
startPoint.set(event.getX(), event.getY()); |
888 |
break;
|
889 |
|
890 |
case MotionEvent.ACTION_POINTER_DOWN:
|
891 |
// Two Fingers down
|
892 |
// Get the spacing of the fingers, 2 fingers
|
893 |
float sx = event.getX(0) - event.getX(1); |
894 |
float sy = event.getY(0) - event.getY(1); |
895 |
startFingerSpacing = (float) Math.sqrt(sx * sx + sy * sy); |
896 |
|
897 |
// Get the midpoint
|
898 |
float xsum = event.getX(0) + event.getX(1); |
899 |
float ysum = event.getY(0) + event.getY(1); |
900 |
startFingerSpacingMidPoint.set(xsum / 2, ysum / 2); |
901 |
|
902 |
mode = ZOOM; |
903 |
setCurrentRegion(null);
|
904 |
updateDisplayImage(); |
905 |
break;
|
906 |
|
907 |
case MotionEvent.ACTION_UP:
|
908 |
// Single Finger Up
|
909 |
setRealtimePreview(true);
|
910 |
needsUpdate = true;
|
911 |
updateDisplayImage(); |
912 |
break;
|
913 |
|
914 |
case MotionEvent.ACTION_MOVE:
|
915 |
|
916 |
// Calculate distance moved
|
917 |
float distance = (float) (Math.sqrt(Math.abs(startPoint.x - event.getX()) + Math.abs(startPoint.y - event.getY()))); |
918 |
//debug(ObscuraApp.TAG,"Move Distance: " + distance);
|
919 |
//debug(ObscuraApp.TAG,"Min Distance: " + minMoveDistance);
|
920 |
|
921 |
// If greater than minMoveDistance, it is likely a drag or zoom
|
922 |
if (distance > minMoveDistance) {
|
923 |
|
924 |
if (mode == TAP || mode == DRAG) {
|
925 |
mode = DRAG; |
926 |
|
927 |
matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y); |
928 |
imageView.setImageMatrix(matrix); |
929 |
// // Reset the start point
|
930 |
startPoint.set(event.getX(), event.getY()); |
931 |
|
932 |
putOnScreen(); |
933 |
//redrawRegions();
|
934 |
|
935 |
handled = true;
|
936 |
|
937 |
} else if (mode == ZOOM) { |
938 |
|
939 |
// Save the current matrix so that if zoom goes to big, we can revert
|
940 |
savedMatrix.set(matrix); |
941 |
|
942 |
if (event.getPointerCount() > 1) { |
943 |
// Get the spacing of the fingers, 2 fingers
|
944 |
float ex = event.getX(0) - event.getX(1); |
945 |
float ey = event.getY(0) - event.getY(1); |
946 |
endFingerSpacing = (float) Math.sqrt(ex * ex + ey * ey); |
947 |
} else {
|
948 |
endFingerSpacing = 0;
|
949 |
} |
950 |
|
951 |
//Log.d(ObscuraApp.TAG, "End Finger Spacing=" + endFingerSpacing);
|
952 |
|
953 |
// If we moved far enough
|
954 |
if (endFingerSpacing > minMoveDistance) {
|
955 |
|
956 |
// Ratio of spacing.. If it was 5 and goes to 10 the image is 2x as big
|
957 |
float scale = endFingerSpacing / startFingerSpacing;
|
958 |
// Scale from the midpoint
|
959 |
matrix.postScale(scale, scale, startFingerSpacingMidPoint.x, startFingerSpacingMidPoint.y); |
960 |
|
961 |
// Make sure that the matrix isn't bigger than max scale/zoom
|
962 |
float[] matrixValues = new float[9]; |
963 |
matrix.getValues(matrixValues); |
964 |
|
965 |
if (matrixValues[0] > MAX_SCALE) { |
966 |
matrix.set(savedMatrix); |
967 |
} |
968 |
imageView.setImageMatrix(matrix); |
969 |
|
970 |
putOnScreen(); |
971 |
//redrawRegions();
|
972 |
|
973 |
// Reset Start Finger Spacing
|
974 |
float esx = event.getX(0) - event.getX(1); |
975 |
float esy = event.getY(0) - event.getY(1); |
976 |
startFingerSpacing = (float) Math.sqrt(esx * esx + esy * esy); |
977 |
//Log.d(ObscuraApp.TAG, "New Start Finger Spacing=" + startFingerSpacing);
|
978 |
|
979 |
// Reset the midpoint
|
980 |
float x_sum = event.getX(0) + event.getX(1); |
981 |
float y_sum = event.getY(0) + event.getY(1); |
982 |
startFingerSpacingMidPoint.set(x_sum / 2, y_sum / 2); |
983 |
|
984 |
handled = true;
|
985 |
} |
986 |
} |
987 |
} |
988 |
break;
|
989 |
} |
990 |
|
991 |
|
992 |
return handled; // indicate event was handled |
993 |
} |
994 |
|
995 |
public void setRealtimePreview(boolean realtimePreview) { |
996 |
if (realtimePreview != doRealtimePreview) {
|
997 |
doRealtimePreview = realtimePreview; |
998 |
needsUpdate = true;
|
999 |
} |
1000 |
} |
1001 |
|
1002 |
/*
|
1003 |
* For live previews
|
1004 |
*/
|
1005 |
public void updateDisplayImage() { |
1006 |
if (needsUpdate) {
|
1007 |
needsUpdate = false;
|
1008 |
if (doRealtimePreview) {
|
1009 |
imageView.setImageBitmap(createObscuredBitmap(imageBitmap.getWidth(), imageBitmap.getHeight(), true));
|
1010 |
} else {
|
1011 |
imageView.setImageBitmap(imageBitmap); |
1012 |
} |
1013 |
} |
1014 |
imageViewOverlay.invalidate(); |
1015 |
} |
1016 |
|
1017 |
public void forceUpdateDisplayImage() { |
1018 |
needsUpdate = true;
|
1019 |
updateDisplayImage(); |
1020 |
} |
1021 |
|
1022 |
/*
|
1023 |
* Move the image onto the screen if it has been moved off
|
1024 |
*/
|
1025 |
public void putOnScreen() { |
1026 |
// Get Rectangle of Tranformed Image
|
1027 |
RectF theRect = getScaleOfImage(); |
1028 |
|
1029 |
debug(ObscuraApp.TAG, theRect.width() + " " + theRect.height());
|
1030 |
|
1031 |
float deltaX = 0, deltaY = 0; |
1032 |
if (theRect.width() < imageView.getWidth()) {
|
1033 |
deltaX = (imageView.getWidth() - theRect.width()) / 2 - theRect.left;
|
1034 |
} else if (theRect.left > 0) { |
1035 |
deltaX = -theRect.left; |
1036 |
} else if (theRect.right < imageView.getWidth()) { |
1037 |
deltaX = imageView.getWidth() - theRect.right; |
1038 |
} |
1039 |
|
1040 |
if (theRect.height() < imageView.getHeight()) {
|
1041 |
deltaY = (imageView.getHeight() - theRect.height()) / 2 - theRect.top;
|
1042 |
} else if (theRect.top > 0) { |
1043 |
deltaY = -theRect.top; |
1044 |
} else if (theRect.bottom < imageView.getHeight()) { |
1045 |
deltaY = imageView.getHeight() - theRect.bottom; |
1046 |
} |
1047 |
|
1048 |
//debug(ObscuraApp.TAG,"Deltas:" + deltaX + " " + deltaY);
|
1049 |
|
1050 |
matrix.postTranslate(deltaX, deltaY); |
1051 |
imageView.setImageMatrix(matrix); |
1052 |
matrix_inverted = new Matrix();
|
1053 |
matrix.invert(matrix_inverted); |
1054 |
updateDisplayImage(); |
1055 |
} |
1056 |
|
1057 |
/*
|
1058 |
* Create new ImageRegion
|
1059 |
*/
|
1060 |
public ImageRegion createImageRegion(float left, float top, float right, float bottom, boolean showPopup, boolean updateNow) { |
1061 |
setCurrentRegion(null);
|
1062 |
|
1063 |
ImageRegion imageRegion = new ImageRegion(
|
1064 |
this,
|
1065 |
left, |
1066 |
top, |
1067 |
right, |
1068 |
bottom, |
1069 |
matrix); |
1070 |
|
1071 |
imageRegions.add(imageRegion); |
1072 |
|
1073 |
if (updateNow) {
|
1074 |
mHandler.post(new Runnable() { |
1075 |
public void run() { |
1076 |
putOnScreen(); |
1077 |
} |
1078 |
}); |
1079 |
} |
1080 |
|
1081 |
return imageRegion;
|
1082 |
} |
1083 |
|
1084 |
/*
|
1085 |
* Delete/Remove specific ImageRegion
|
1086 |
*/
|
1087 |
public void deleteRegion(ImageRegion ir) { |
1088 |
imageRegions.remove(ir); |
1089 |
//redrawRegions();
|
1090 |
needsUpdate = true;
|
1091 |
updateDisplayImage(); |
1092 |
} |
1093 |
|
1094 |
/*
|
1095 |
* Returns the Rectangle of Tranformed Image
|
1096 |
*/
|
1097 |
public RectF getScaleOfImage() {
|
1098 |
RectF theRect = new RectF(0, 0, imageBitmap.getWidth(), imageBitmap.getHeight()); |
1099 |
matrix.mapRect(theRect); |
1100 |
return theRect;
|
1101 |
} |
1102 |
|
1103 |
|
1104 |
/*
|
1105 |
* Handles normal onClicks for buttons registered to this.
|
1106 |
* Currently only the zoomIn and zoomOut buttons
|
1107 |
*/
|
1108 |
@Override
|
1109 |
public void onClick(View v) { |
1110 |
|
1111 |
if (v == zoomIn) {
|
1112 |
float scale = 1.5f; |
1113 |
|
1114 |
PointF midpoint = new PointF(imageView.getWidth() / 2, imageView.getHeight() / 2); |
1115 |
matrix.postScale(scale, scale, midpoint.x, midpoint.y); |
1116 |
imageView.setImageMatrix(matrix); |
1117 |
putOnScreen(); |
1118 |
} else if (v == zoomOut) { |
1119 |
float scale = 0.75f; |
1120 |
|
1121 |
PointF midpoint = new PointF(imageView.getWidth() / 2, imageView.getHeight() / 2); |
1122 |
matrix.postScale(scale, scale, midpoint.x, midpoint.y); |
1123 |
imageView.setImageMatrix(matrix); |
1124 |
|
1125 |
putOnScreen(); |
1126 |
} else if (v == btnNew) { |
1127 |
newDefaultRegion(); |
1128 |
} else if (v == btnPreview) { |
1129 |
showPreview(true);
|
1130 |
} else if (v == btnSave) { |
1131 |
//Why does this not show?
|
1132 |
mProgressDialog = ProgressDialog.show(this, "", "Saving...", true, true); |
1133 |
|
1134 |
mHandler.postDelayed(new Runnable() { |
1135 |
@Override
|
1136 |
public void run() { |
1137 |
// this will be done in the Pipeline Thread
|
1138 |
checkWritePermissionThenSave(); |
1139 |
} |
1140 |
}, 500);
|
1141 |
} else if (v == btnShare) { |
1142 |
// Share Image
|
1143 |
shareImage(); |
1144 |
} else if (mode != DRAG && mode != ZOOM) { |
1145 |
// Don't create new areas when previewing!
|
1146 |
if (!isPreviewing()) {
|
1147 |
float defaultSize = imageView.getWidth() / 4; |
1148 |
float halfSize = defaultSize / 2; |
1149 |
|
1150 |
RectF newBox = new RectF();
|
1151 |
|
1152 |
newBox.left = startPoint.x - halfSize; |
1153 |
newBox.top = startPoint.y - halfSize; |
1154 |
|
1155 |
newBox.right = startPoint.x + halfSize; |
1156 |
newBox.bottom = startPoint.y + halfSize; |
1157 |
|
1158 |
Matrix iMatrix = new Matrix();
|
1159 |
matrix.invert(iMatrix); |
1160 |
iMatrix.mapRect(newBox); |
1161 |
|
1162 |
ImageRegion region = createImageRegion(newBox.left, newBox.top, newBox.right, newBox.bottom, true, true); |
1163 |
setCurrentRegion(region); |
1164 |
} |
1165 |
} |
1166 |
|
1167 |
} |
1168 |
|
1169 |
private boolean isPreviewing() { |
1170 |
return imageViewOverlay.getVisibility() == View.GONE; |
1171 |
} |
1172 |
|
1173 |
/*
|
1174 |
* Standard method for menu items. Uses res/menu/image_editor_menu.xml
|
1175 |
*/
|
1176 |
@Override
|
1177 |
public boolean onCreateOptionsMenu(Menu menu) { |
1178 |
|
1179 |
MenuInflater inflater = getMenuInflater(); |
1180 |
inflater.inflate(R.menu.image_editor_menu, menu); |
1181 |
|
1182 |
return true; |
1183 |
} |
1184 |
|
1185 |
private void newDefaultRegion() { |
1186 |
// Set the Start point.
|
1187 |
startPoint.set(imageView.getWidth() / 2, imageView.getHeight() / 2); |
1188 |
|
1189 |
float defaultSize = imageView.getWidth() / 4; |
1190 |
float halfSize = defaultSize / 2; |
1191 |
|
1192 |
RectF newRegion = new RectF();
|
1193 |
|
1194 |
newRegion.left = startPoint.x - halfSize; |
1195 |
newRegion.top = startPoint.y - halfSize; |
1196 |
|
1197 |
newRegion.right = startPoint.x + defaultSize; |
1198 |
newRegion.left = startPoint.y + defaultSize; |
1199 |
|
1200 |
Matrix iMatrix = new Matrix();
|
1201 |
matrix.invert(iMatrix); |
1202 |
iMatrix.mapRect(newRegion); |
1203 |
|
1204 |
createImageRegion(newRegion.left, newRegion.top, newRegion.right, newRegion.bottom, false, true); |
1205 |
|
1206 |
} |
1207 |
|
1208 |
/*
|
1209 |
* Normal menu item selected method. Uses menu items defined in XML: res/menu/image_editor_menu.xml
|
1210 |
*/
|
1211 |
@Override
|
1212 |
public boolean onOptionsItemSelected(MenuItem item) { |
1213 |
|
1214 |
switch (item.getItemId()) {
|
1215 |
|
1216 |
case R.id.menu_save:
|
1217 |
|
1218 |
//Why does this not show?
|
1219 |
mProgressDialog = ProgressDialog.show(this, "", "Saving...", true, true); |
1220 |
|
1221 |
mHandler.postDelayed(new Runnable() { |
1222 |
@Override
|
1223 |
public void run() { |
1224 |
// this will be done in the Pipeline Thread
|
1225 |
checkWritePermissionThenSave(); |
1226 |
} |
1227 |
}, 500);
|
1228 |
|
1229 |
|
1230 |
return true; |
1231 |
|
1232 |
case R.id.menu_share:
|
1233 |
// Share Image
|
1234 |
shareImage(); |
1235 |
|
1236 |
|
1237 |
return true; |
1238 |
|
1239 |
/*
|
1240 |
case R.id.menu_delete_original:
|
1241 |
// Delete Original Image
|
1242 |
handleDelete();
|
1243 |
|
1244 |
return true;
|
1245 |
*/
|
1246 |
case android.R.id.home:
|
1247 |
// Pull up about screen
|
1248 |
finish(); |
1249 |
|
1250 |
return true; |
1251 |
|
1252 |
case R.id.menu_preview:
|
1253 |
boolean checked = item.isChecked();
|
1254 |
item.setChecked(!checked); |
1255 |
showPreview(!checked); |
1256 |
return true; |
1257 |
|
1258 |
default:
|
1259 |
return false; |
1260 |
} |
1261 |
} |
1262 |
|
1263 |
/*
|
1264 |
* Display the about screen
|
1265 |
*/
|
1266 |
private void displayAbout() { |
1267 |
|
1268 |
StringBuffer msg = new StringBuffer(); |
1269 |
|
1270 |
msg.append(getString(R.string.app_name)); |
1271 |
|
1272 |
String versNum = ""; |
1273 |
|
1274 |
try {
|
1275 |
String pkg = getPackageName();
|
1276 |
versNum = getPackageManager().getPackageInfo(pkg, 0).versionName;
|
1277 |
} catch (Exception e) { |
1278 |
versNum = "";
|
1279 |
} |
1280 |
|
1281 |
msg.append(" v" + versNum);
|
1282 |
msg.append('\n');
|
1283 |
msg.append('\n');
|
1284 |
|
1285 |
msg.append(getString(R.string.about)); |
1286 |
|
1287 |
msg.append('\n');
|
1288 |
msg.append('\n');
|
1289 |
|
1290 |
msg.append(getString(R.string.about2)); |
1291 |
|
1292 |
msg.append('\n');
|
1293 |
msg.append('\n');
|
1294 |
|
1295 |
msg.append(getString(R.string.about3)); |
1296 |
|
1297 |
showDialog(msg.toString()); |
1298 |
} |
1299 |
|
1300 |
private void showDialog(String msg) { |
1301 |
new AlertDialog.Builder(this) |
1302 |
.setTitle(getString(R.string.app_name)) |
1303 |
.setMessage(msg) |
1304 |
.create().show(); |
1305 |
} |
1306 |
|
1307 |
/*
|
1308 |
* Display preview image
|
1309 |
*/
|
1310 |
private void showPreview(boolean preview) { |
1311 |
imageViewOverlay.setVisibility(preview ? View.GONE : View.VISIBLE); |
1312 |
// setCurrentRegion(null);
|
1313 |
} |
1314 |
|
1315 |
/*
|
1316 |
* When the user selects the Share menu item
|
1317 |
* Uses saveTmpImage (overwriting what is already there) and uses the standard Android Share Intent
|
1318 |
*/
|
1319 |
private void shareImage() { |
1320 |
Uri tmpImageUri; |
1321 |
|
1322 |
if ((tmpImageUri = saveTmpImage()) != null) { |
1323 |
Intent share = new Intent(Intent.ACTION_SEND);
|
1324 |
share.setType("image/jpeg");
|
1325 |
share.putExtra(Intent.EXTRA_STREAM, tmpImageUri); |
1326 |
startActivity(Intent.createChooser(share, "Share Image"));
|
1327 |
} else {
|
1328 |
Toast t = Toast.makeText(this, "Saving Temporary File Failed!", Toast.LENGTH_SHORT); |
1329 |
t.show(); |
1330 |
} |
1331 |
} |
1332 |
|
1333 |
/*
|
1334 |
* When the user selects the Share menu item
|
1335 |
* Uses saveTmpImage (overwriting what is already there) and uses the standard Android Share Intent
|
1336 |
*/
|
1337 |
private void viewImage(Uri imgView) { |
1338 |
|
1339 |
Intent iView = new Intent(Intent.ACTION_VIEW);
|
1340 |
iView.setDataAndType(imgView, MIME_TYPE_JPEG); |
1341 |
iView.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
1342 |
|
1343 |
startActivity(Intent.createChooser(iView, "View Image"));
|
1344 |
|
1345 |
} |
1346 |
|
1347 |
|
1348 |
/*
|
1349 |
* Goes through the regions that have been defined and creates a bitmap with them obscured.
|
1350 |
* This may introduce memory issues and therefore have to be done in a different manner.
|
1351 |
*/
|
1352 |
private Bitmap createObscuredBitmap(int width, int height, boolean showBorders) { |
1353 |
if (imageBitmap == null) |
1354 |
return null; |
1355 |
|
1356 |
if (obscuredBmp == null || (obscuredBmp.getWidth() != width)) { |
1357 |
// Create the bitmap that we'll output from this method
|
1358 |
Bitmap.Config config = imageBitmap.getConfig(); |
1359 |
if (config == null) { |
1360 |
config = Bitmap.Config.RGB_565;//TODO
|
1361 |
} |
1362 |
obscuredBmp = Bitmap.createBitmap(width, height, config); |
1363 |
|
1364 |
// Create the canvas to draw on
|
1365 |
obscuredCanvas = new Canvas(obscuredBmp); |
1366 |
} |
1367 |
|
1368 |
// Create the paint used to draw with
|
1369 |
obscuredPaint = new Paint(); |
1370 |
// Create a default matrix
|
1371 |
Matrix obscuredMatrix = new Matrix();
|
1372 |
// Draw the scaled image on the new bitmap
|
1373 |
obscuredCanvas.drawBitmap(imageBitmap, obscuredMatrix, obscuredPaint); |
1374 |
|
1375 |
// Iterate through the regions that have been created
|
1376 |
Iterator<ImageRegion> i = imageRegions.iterator();
|
1377 |
while (i.hasNext()) {
|
1378 |
ImageRegion currentRegion = i.next(); |
1379 |
RegionProcesser om = currentRegion.getRegionProcessor(); |
1380 |
|
1381 |
RectF regionRect = new RectF(currentRegion.getBounds());
|
1382 |
|
1383 |
if (doRealtimePreview)
|
1384 |
om.processRegion(regionRect, obscuredCanvas, obscuredBmp); |
1385 |
} |
1386 |
|
1387 |
return obscuredBmp;
|
1388 |
} |
1389 |
|
1390 |
|
1391 |
private boolean canDoNative() { |
1392 |
if (originalImageUri == null) |
1393 |
return false; |
1394 |
|
1395 |
// Iterate through the regions that have been created
|
1396 |
Iterator<ImageRegion> i = imageRegions.iterator();
|
1397 |
while (i.hasNext()) {
|
1398 |
ImageRegion iRegion = i.next(); |
1399 |
if (iRegion.getRegionProcessor() instanceof MaskObscure) |
1400 |
return false; |
1401 |
} |
1402 |
|
1403 |
return true; |
1404 |
|
1405 |
} |
1406 |
|
1407 |
/*
|
1408 |
* Goes through the regions that have been defined and creates a bitmap with them obscured.
|
1409 |
* This may introduce memory issues and therefore have to be done in a different manner.
|
1410 |
*/
|
1411 |
private Uri processNativeRes(Uri sourceImage) throws Exception { |
1412 |
|
1413 |
File tmpInFile = new File(getCacheDir(), 'i' + TMP_FILE_NAME); |
1414 |
File fileTarget = new File(getCacheDir(), TMP_FILE_NAME); |
1415 |
|
1416 |
if (tmpInFile.exists())
|
1417 |
tmpInFile.delete(); |
1418 |
|
1419 |
if (fileTarget.exists())
|
1420 |
fileTarget.delete(); |
1421 |
|
1422 |
copy(sourceImage, tmpInFile); |
1423 |
|
1424 |
JpegRedaction om = new JpegRedaction();
|
1425 |
om.setFiles(tmpInFile, fileTarget); |
1426 |
om.processRegions(imageRegions, inSampleSize, obscuredCanvas); |
1427 |
|
1428 |
if (!fileTarget.exists())
|
1429 |
throw new Exception("native proc failed"); |
1430 |
|
1431 |
return Uri.fromFile(fileTarget);
|
1432 |
} |
1433 |
|
1434 |
private void copy(Uri uriSrc, File fileTarget) throws IOException { |
1435 |
InputStream is = null; |
1436 |
|
1437 |
try {
|
1438 |
is = getContentResolver().openInputStream(uriSrc); |
1439 |
} |
1440 |
catch (FileNotFoundException fe) |
1441 |
{ |
1442 |
is = new FileInputStream(uriSrc.getPath()); |
1443 |
} |
1444 |
|
1445 |
OutputStream os = new FileOutputStream(fileTarget); |
1446 |
|
1447 |
copyStreams(is, os); |
1448 |
|
1449 |
|
1450 |
} |
1451 |
|
1452 |
private void copy(Uri uriSrc, Uri uriTarget) throws IOException { |
1453 |
|
1454 |
InputStream is = getContentResolver().openInputStream(uriSrc);
|
1455 |
|
1456 |
OutputStream os = getContentResolver().openOutputStream(uriTarget);
|
1457 |
|
1458 |
copyStreams(is, os); |
1459 |
|
1460 |
|
1461 |
} |
1462 |
|
1463 |
private static void copyStreams(InputStream input, OutputStream output) throws IOException { |
1464 |
// if both are file streams, use channel IO
|
1465 |
if ((output instanceof FileOutputStream) && (input instanceof FileInputStream)) { |
1466 |
try {
|
1467 |
FileChannel target = ((FileOutputStream) output).getChannel(); |
1468 |
FileChannel source = ((FileInputStream) input).getChannel(); |
1469 |
|
1470 |
source.transferTo(0, Integer.MAX_VALUE, target); |
1471 |
|
1472 |
source.close(); |
1473 |
target.close(); |
1474 |
|
1475 |
return;
|
1476 |
} catch (Exception e) { /* failover to byte stream version */ |
1477 |
} |
1478 |
} |
1479 |
|
1480 |
byte[] buf = new byte[8192]; |
1481 |
while (true) { |
1482 |
int length = input.read(buf);
|
1483 |
if (length < 0) |
1484 |
break;
|
1485 |
output.write(buf, 0, length);
|
1486 |
} |
1487 |
|
1488 |
try {
|
1489 |
input.close(); |
1490 |
} catch (IOException ignore) { |
1491 |
} |
1492 |
try {
|
1493 |
output.close(); |
1494 |
} catch (IOException ignore) { |
1495 |
} |
1496 |
} |
1497 |
|
1498 |
/*
|
1499 |
* Save a temporary image for sharing only
|
1500 |
*/
|
1501 |
private Uri saveTmpImage() {
|
1502 |
|
1503 |
String storageState = Environment.getExternalStorageState();
|
1504 |
if (!Environment.MEDIA_MOUNTED.equals(storageState)) {
|
1505 |
Toast t = Toast.makeText(this, "External storage not available", Toast.LENGTH_SHORT); |
1506 |
t.show(); |
1507 |
return null; |
1508 |
} |
1509 |
|
1510 |
// Create the bitmap that will be saved
|
1511 |
// Perhaps this should be smaller than screen size??
|
1512 |
int w = imageBitmap.getWidth();
|
1513 |
int h = imageBitmap.getHeight();
|
1514 |
Bitmap obscuredBmp = createObscuredBitmap(w, h, false);
|
1515 |
|
1516 |
// Create the Uri - This can't be "private"
|
1517 |
File tmpFileDirectory = new File(Environment.getExternalStorageDirectory().getPath() + TMP_FILE_DIRECTORY); |
1518 |
File tmpFile = new File(tmpFileDirectory, TMP_FILE_NAME); |
1519 |
debug(ObscuraApp.TAG, tmpFile.getPath()); |
1520 |
|
1521 |
try {
|
1522 |
if (!tmpFileDirectory.exists()) {
|
1523 |
tmpFileDirectory.mkdirs(); |
1524 |
} |
1525 |
Uri tmpImageUri = Uri.fromFile(tmpFile); |
1526 |
|
1527 |
OutputStream imageFileOS;
|
1528 |
|
1529 |
int quality = 100; |
1530 |
imageFileOS = getContentResolver().openOutputStream(tmpImageUri); |
1531 |
obscuredBmp.compress(CompressFormat.JPEG, quality, imageFileOS); |
1532 |
|
1533 |
return tmpImageUri;
|
1534 |
} catch (FileNotFoundException e) { |
1535 |
mProgressDialog.cancel(); |
1536 |
e.printStackTrace(); |
1537 |
return null; |
1538 |
} |
1539 |
} |
1540 |
|
1541 |
private void checkWritePermissionThenSave() { |
1542 |
int permissionCheck = ContextCompat.checkSelfPermission(this, |
1543 |
Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
1544 |
if (Build.VERSION.SDK_INT <= 18) |
1545 |
permissionCheck = PackageManager.PERMISSION_GRANTED; // For old devices we ask in the manifest!
|
1546 |
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
1547 |
ActivityCompat.requestPermissions(this, new String[]{ |
1548 |
Manifest.permission.READ_EXTERNAL_STORAGE,
|
1549 |
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
1550 |
WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST); |
1551 |
} else {
|
1552 |
saveImage(); |
1553 |
} |
1554 |
} |
1555 |
|
1556 |
@Override
|
1557 |
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { |
1558 |
switch (requestCode) {
|
1559 |
case WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST: {
|
1560 |
// If request is cancelled, the result arrays are empty.
|
1561 |
if (grantResults.length > 1 |
1562 |
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
1563 |
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
1564 |
saveImage(); |
1565 |
} |
1566 |
} |
1567 |
break;
|
1568 |
} |
1569 |
} |
1570 |
|
1571 |
/*
|
1572 |
* The method that actually saves the altered image.
|
1573 |
* This in combination with createObscuredBitmap could/should be done in another, more memory efficient manner.
|
1574 |
*/
|
1575 |
private boolean saveImage() { |
1576 |
|
1577 |
SimpleDateFormat dateFormat = new SimpleDateFormat(EXPORT_DATE_FORMAT); |
1578 |
Date date = new Date(); |
1579 |
String dateString = dateFormat.format(date);
|
1580 |
|
1581 |
/**
|
1582 |
ContentValues cv = new ContentValues();
|
1583 |
|
1584 |
// Add a date so it shows up in a reasonable place in the gallery - Should we do this??
|
1585 |
|
1586 |
// Which one?
|
1587 |
cv.put(Images.Media.DATE_ADDED, dateString);
|
1588 |
cv.put(Images.Media.DATE_TAKEN, dateString);
|
1589 |
cv.put(Images.Media.DATE_MODIFIED, dateString);
|
1590 |
cv.put(Images.Media.TITLE, dateString);
|
1591 |
// cv.put(Images.Media.BUCKET_ID, "ObscuraCam");
|
1592 |
// cv.put(Images.Media.DESCRIPTION, "ObscuraCam");
|
1593 |
//cv.put(Images.Media.CONTENT_TYPE, MIME_TYPE_JPEG);
|
1594 |
|
1595 |
// Uri is savedImageUri which is global
|
1596 |
// Create the Uri, this should put it in the gallery
|
1597 |
// New Each time
|
1598 |
savedImageUri = getContentResolver().insert(
|
1599 |
Media.EXTERNAL_CONTENT_URI, cv);
|
1600 |
|
1601 |
if (savedImageUri == null)
|
1602 |
return false;
|
1603 |
**/
|
1604 |
|
1605 |
File fileImageOut =
|
1606 |
new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), |
1607 |
"obscura" + new Date().getTime() + ".jpg"); |
1608 |
|
1609 |
savedImageUri = Uri.fromFile(fileImageOut); |
1610 |
|
1611 |
boolean nativeSuccess = false; |
1612 |
|
1613 |
/**
|
1614 |
if (canDoNative()) {
|
1615 |
try {
|
1616 |
Uri savedNativeTmp = processNativeRes(originalImageUri);
|
1617 |
|
1618 |
copy(savedNativeTmp, savedImageUri);
|
1619 |
|
1620 |
nativeSuccess = true;
|
1621 |
|
1622 |
} catch (Exception e) {
|
1623 |
Log.e(ObscuraApp.TAG, "error doing native redact", e);
|
1624 |
}
|
1625 |
}**/
|
1626 |
|
1627 |
|
1628 |
if (!nativeSuccess) {
|
1629 |
try {
|
1630 |
|
1631 |
obscuredBmp = createObscuredBitmap(imageBitmap.getWidth(), imageBitmap.getHeight(), false);
|
1632 |
|
1633 |
OutputStream imageFileOS;
|
1634 |
|
1635 |
int quality = 100; //lossless? good question - still a smaller version |
1636 |
imageFileOS = getContentResolver().openOutputStream(savedImageUri); |
1637 |
obscuredBmp.compress(CompressFormat.JPEG, quality, imageFileOS); |
1638 |
|
1639 |
} catch (Exception e) { |
1640 |
Log.e(ObscuraApp.TAG, "error doing redact", e);
|
1641 |
return false; |
1642 |
} |
1643 |
|
1644 |
} |
1645 |
|
1646 |
/**
|
1647 |
// package and insert exif data
|
1648 |
mp = new MetadataParser(dateFormat.format(date), pullPathFromUri(savedImageUri), this);
|
1649 |
Iterator<ImageRegion> i = imageRegions.iterator();
|
1650 |
while (i.hasNext()) {
|
1651 |
mp.addRegion(i.next().getRegionProcessor().getProperties());
|
1652 |
}
|
1653 |
|
1654 |
mp.flushMetadata();
|
1655 |
**/
|
1656 |
|
1657 |
Intent intent = |
1658 |
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
1659 |
intent.setData(savedImageUri); |
1660 |
sendBroadcast(intent); |
1661 |
|
1662 |
mProgressDialog.cancel(); |
1663 |
|
1664 |
// showDeleteOriginalDialog();
|
1665 |
|
1666 |
Snackbar snackbar = Snackbar.make(findViewById(R.id.frameRoot),R.string.processing_complete,Snackbar.LENGTH_INDEFINITE); |
1667 |
snackbar.setAction("Open", new OnClickListener() { |
1668 |
@Override
|
1669 |
public void onClick(View view) { |
1670 |
viewImage(savedImageUri); |
1671 |
} |
1672 |
}); |
1673 |
snackbar.show(); |
1674 |
|
1675 |
return true; |
1676 |
} |
1677 |
|
1678 |
// Queries the contentResolver to pull out the path for the actual file.
|
1679 |
/* This code is currently unused but i often find myself needing it so I
|
1680 |
* am placing it here for safe keeping ;-) */
|
1681 |
|
1682 |
/*
|
1683 |
* Yep, uncommenting it back out so we can use the original path to refresh media scanner
|
1684 |
* HNH 8/23/11
|
1685 |
*/
|
1686 |
public File pullPathFromUri(Uri originalUri) { |
1687 |
|
1688 |
String originalImageFilePath = originalUri.toString();
|
1689 |
|
1690 |
if (originalUri.getScheme() != null && originalUri.getScheme().equals("file")) { |
1691 |
originalImageFilePath = originalUri.toString(); |
1692 |
} else {
|
1693 |
String[] columnsToSelect = {MediaStore.Images.Media.DATA}; |
1694 |
Cursor imageCursor = getContentResolver().query(originalUri, columnsToSelect, null, null, null); |
1695 |
if (imageCursor != null) { |
1696 |
if (imageCursor.moveToFirst()) {
|
1697 |
originalImageFilePath = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA)); |
1698 |
} |
1699 |
imageCursor.close(); |
1700 |
} |
1701 |
} |
1702 |
|
1703 |
return new File(originalImageFilePath); |
1704 |
} |
1705 |
|
1706 |
|
1707 |
/*
|
1708 |
* Handling screen configuration changes ourselves, we don't want the activity to restart on rotation
|
1709 |
*/
|
1710 |
@Override
|
1711 |
public void onConfigurationChanged(Configuration conf) { |
1712 |
super.onConfigurationChanged(conf);
|
1713 |
|
1714 |
mHandler.postDelayed(new Runnable() { |
1715 |
public void run() { |
1716 |
putOnScreen(); |
1717 |
} |
1718 |
}, 100);
|
1719 |
|
1720 |
} |
1721 |
|
1722 |
|
1723 |
@Override
|
1724 |
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
1725 |
if (resultCode == Activity.RESULT_OK) {
|
1726 |
if (requestCode == FROM_INFORMA) {
|
1727 |
// replace corresponding image region
|
1728 |
@SuppressWarnings("unchecked") |
1729 |
HashMap<String, String> informaReturn = (HashMap<String, String>) data.getSerializableExtra("informaReturn"); |
1730 |
Properties mProp = imageRegions.get(data.getIntExtra("irIndex", 0)).getRegionProcessor().getProperties(); |
1731 |
|
1732 |
// iterate through returned hashmap and place these new properties in it.
|
1733 |
for (Map.Entry<String, String> entry : informaReturn.entrySet()) { |
1734 |
mProp.setProperty(entry.getKey(), entry.getValue()); |
1735 |
} |
1736 |
|
1737 |
imageRegions.get(data.getIntExtra("irIndex", 0)).getRegionProcessor().setProperties(mProp); |
1738 |
|
1739 |
} |
1740 |
} |
1741 |
} |
1742 |
|
1743 |
@Override
|
1744 |
protected void onPostResume() { |
1745 |
super.onPostResume();
|
1746 |
|
1747 |
} |
1748 |
|
1749 |
public Paint getPainter() { |
1750 |
return obscuredPaint;
|
1751 |
} |
1752 |
|
1753 |
private void debug(String tag, String message) { |
1754 |
Log.d(tag, message); |
1755 |
} |
1756 |
|
1757 |
|
1758 |
public ImageView getImageView() { |
1759 |
return imageView;
|
1760 |
} |
1761 |
|
1762 |
@Override
|
1763 |
public void onAttachedToWindow() { |
1764 |
super.onAttachedToWindow();
|
1765 |
Window window = getWindow();
|
1766 |
window.setFormat(PixelFormat.RGBA_8888); |
1767 |
window.getDecorView().getBackground().setDither(true);
|
1768 |
|
1769 |
} |
1770 |
|
1771 |
private InputStream streamFromUri(Uri uri) { |
1772 |
InputStream is = null; |
1773 |
try {
|
1774 |
if (uri.getScheme() != null && uri.getScheme().contentEquals("content")) |
1775 |
is = getContentResolver().openInputStream(uri); |
1776 |
else
|
1777 |
is = new FileInputStream(new File(uri.toString())); |
1778 |
} catch (Exception e) { |
1779 |
e.printStackTrace(); |
1780 |
} |
1781 |
return is;
|
1782 |
} |
1783 |
|
1784 |
@Override
|
1785 |
public void onOptionSelected(int operation) { |
1786 |
if (getCurrentRegion() != null) { |
1787 |
if (operation == -1) { |
1788 |
deleteRegion(getCurrentRegion()); |
1789 |
setCurrentRegion(null);
|
1790 |
updateDisplayImage(); |
1791 |
} else {
|
1792 |
getCurrentRegion().setObscureType(operation); |
1793 |
((ImageRegionOptionsRecyclerViewAdapter)recyclerViewRegionOptions.getAdapter()).setCurrentItem(getCurrentRegion().mObscureType); |
1794 |
forceUpdateDisplayImage(); |
1795 |
} |
1796 |
} |
1797 |
} |
1798 |
|
1799 |
|
1800 |
private class RegionOverlayView extends View implements OnTouchListener { |
1801 |
private Paint paint; |
1802 |
private RectF mappedRect;
|
1803 |
private int mode; |
1804 |
private final int selectionHandleRadius; |
1805 |
|
1806 |
public RegionOverlayView(Context context) { |
1807 |
super(context);
|
1808 |
paint = new Paint(); |
1809 |
paint.setStyle(Style.STROKE);
|
1810 |
Resources r = context.getResources(); |
1811 |
int selectionWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SELECTION_BORDER_WIDTH, r.getDisplayMetrics()); |
1812 |
selectionHandleRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SELECTION_HANDLE_RADIUS, r.getDisplayMetrics());
|
1813 |
paint.setStrokeWidth(selectionWidth); |
1814 |
mappedRect = new RectF();
|
1815 |
setOnTouchListener(this);
|
1816 |
} |
1817 |
|
1818 |
@Override
|
1819 |
protected void onDraw(Canvas canvas) { |
1820 |
super.onDraw(canvas);
|
1821 |
Matrix imageMatrix = imageView.getImageMatrix(); |
1822 |
try {
|
1823 |
for (ImageRegion currentRegion : imageRegions) {
|
1824 |
RectF regionRect = currentRegion.getBounds(); |
1825 |
imageMatrix.mapRect(mappedRect, regionRect); |
1826 |
if (currentRegion.isSelected()) {
|
1827 |
paint.setColor(0xffb6ff66);
|
1828 |
paint.setStrokeWidth(3f);
|
1829 |
} |
1830 |
else {
|
1831 |
paint.setColor(Color.WHITE);
|
1832 |
paint.setStrokeWidth(1f);
|
1833 |
} |
1834 |
|
1835 |
paint.setStyle(Style.STROKE);
|
1836 |
canvas.drawRect(mappedRect, paint); |
1837 |
|
1838 |
// Draw drag handles
|
1839 |
paint.setStyle(Style.FILL);
|
1840 |
if (currentRegion.isSelected()) {
|
1841 |
canvas.drawCircle(mappedRect.centerX(), mappedRect.top, selectionHandleRadius, paint); |
1842 |
canvas.drawCircle(mappedRect.centerX(), mappedRect.bottom, selectionHandleRadius, paint); |
1843 |
canvas.drawCircle(mappedRect.left, mappedRect.centerY(), selectionHandleRadius, paint); |
1844 |
canvas.drawCircle(mappedRect.right, mappedRect.centerY(), selectionHandleRadius, paint); |
1845 |
} |
1846 |
} |
1847 |
} catch (Exception ignored) { |
1848 |
} |
1849 |
} |
1850 |
|
1851 |
|
1852 |
@Override
|
1853 |
public boolean onTouch(View v, MotionEvent event) { |
1854 |
if (currRegion != null && (mode == DRAG || currRegion.containsPoint(event.getX(), event.getY()))) |
1855 |
return onTouchRegion(v, event, currRegion);
|
1856 |
else
|
1857 |
return onTouchImage(v, event);
|
1858 |
} |
1859 |
|
1860 |
public ImageRegion findRegion(MotionEvent event) {
|
1861 |
ImageRegion result = null;
|
1862 |
for (ImageRegion region : imageRegions) {
|
1863 |
if (region.containsPoint(event.getX(), event.getY())) {
|
1864 |
result = region; |
1865 |
break;
|
1866 |
} |
1867 |
} |
1868 |
return result;
|
1869 |
} |
1870 |
|
1871 |
public boolean onTouchRegion(View v, MotionEvent event, ImageRegion iRegion) { |
1872 |
boolean handled = false; |
1873 |
|
1874 |
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
1875 |
case MotionEvent.ACTION_DOWN:
|
1876 |
setRealtimePreview(false);
|
1877 |
iRegion.setCornerMode(event.getX(), event.getY()); |
1878 |
mode = DRAG; |
1879 |
handled = iRegion.onTouch(v, event); |
1880 |
|
1881 |
break;
|
1882 |
|
1883 |
case MotionEvent.ACTION_UP:
|
1884 |
case MotionEvent.ACTION_CANCEL:
|
1885 |
mode = NONE; |
1886 |
handled = iRegion.onTouch(v, event); |
1887 |
setRealtimePreview(true);
|
1888 |
updateDisplayImage(); |
1889 |
|
1890 |
break;
|
1891 |
|
1892 |
case MotionEvent.ACTION_MOVE:
|
1893 |
mode = DRAG; |
1894 |
handled = iRegion.onTouch(v, event); |
1895 |
|
1896 |
break;
|
1897 |
|
1898 |
default:
|
1899 |
mode = NONE; |
1900 |
|
1901 |
} |
1902 |
|
1903 |
return handled;
|
1904 |
|
1905 |
|
1906 |
} |
1907 |
|
1908 |
public boolean onTouchImage(View v, MotionEvent event) { |
1909 |
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
1910 |
case MotionEvent.ACTION_DOWN:
|
1911 |
ImageRegion newRegion = findRegion(event); |
1912 |
if (newRegion != null) { |
1913 |
setCurrentRegion(newRegion); |
1914 |
updateDisplayImage(); |
1915 |
return onTouchRegion(v, event, newRegion);
|
1916 |
} else {
|
1917 |
if (getCurrentRegion() != null) { |
1918 |
setCurrentRegion(null);
|
1919 |
forceUpdateDisplayImage(); |
1920 |
return true; |
1921 |
} |
1922 |
return false; |
1923 |
} |
1924 |
case MotionEvent.ACTION_CANCEL:
|
1925 |
case MotionEvent.ACTION_UP:
|
1926 |
setRealtimePreview(true);
|
1927 |
updateDisplayImage(); |
1928 |
} |
1929 |
return false; |
1930 |
} |
1931 |
} |
1932 |
} |