/* Copyright (C) 2014,2015 Jan Müller, Björn Stelter
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package de.hu_berlin.informatik.spws2014.mapever.entzerrung;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import de.hu_berlin.informatik.spws2014.mapever.BaseActivity;
import de.hu_berlin.informatik.spws2014.mapever.FileUtils;
import de.hu_berlin.informatik.spws2014.mapever.MapEverApp;
import de.hu_berlin.informatik.spws2014.mapever.R;
import de.hu_berlin.informatik.spws2014.mapever.navigation.Navigation;
public class Entzerren extends BaseActivity {
// savedInstanceState constants
private static final String SHOWHELPSAVED = "SHOWHELPSAVED";
private static final String IMAGEENTZERRT = "IMAGEENTZERRT";
// other constants
private static final String INPUTFILENAME = MapEverApp.TEMP_IMAGE_FILENAME;
private static final String INPUTFILENAMEBAK = INPUTFILENAME + "_bak";
// View references
private EntzerrungsView entzerrungsView;
private FrameLayout layoutFrame;
// various state variables
private boolean entzerrt = false; // ist mindestens einmal entzerrt worden?
private boolean tutorial = false; // Quickhelp aktiv?
private boolean loading_active = false; // Entzerrungsvorgang aktiv?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_entzerren);
// Layout aufbauen
layoutFrame = new FrameLayout(getBaseContext());
setContentView(layoutFrame);
getLayoutInflater().inflate(R.layout.activity_entzerren, layoutFrame);
// Referenz auf EntzerrungsView speichern
entzerrungsView = (EntzerrungsView) findViewById(R.id.entzerrungsview);
// Bild in die View laden
loadImageFile();
// Wird die Activity frisch neu erstellt oder haben wir einen gespeicherten Zustand?
if (savedInstanceState == null) {
// Verwende statischen Dateinamen als Eingabe
File imageFile = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAME));
File imageFile_bak = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAMEBAK));
// Backup von der Datei erstellen, um ein Rückgängigmachen zu ermöglichen
copy(imageFile, imageFile_bak);
}
else {
// Zustandsvariablen wiederherstellen
entzerrt = savedInstanceState.getBoolean(IMAGEENTZERRT);
boolean _tutorial = savedInstanceState.getBoolean(SHOWHELPSAVED);
if (_tutorial) {
startQuickHelp();
}
}
// EntzerrungsView updaten und neu zeichnen lassen
entzerrungsView.update();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.entzerren, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (loading_active)
return false;
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch (item.getItemId()) {
case R.id.action_quick_help:
// Schnellhilfe-Button
if (tutorial) {
endQuickHelp();
}
else {
startQuickHelp();
}
return true;
case R.id.action_show_corners:
// Toggle showCorners
if (entzerrungsView.isShowingCorners()) {
entzerrungsView.showCorners(false);
}
else if (entzerrungsView.isImageTypeSupported()) {
entzerrungsView.showCorners(true);
}
else if (entzerrungsView.isOpenCVLoadError()) {
showErrorMessage(R.string.deskewing_opencv_not_available);
}
else {
// Image type is not supported by deskewing algorithm (GIF?) so don't allow deskewing
showErrorMessage(R.string.deskewing_imagetype_not_supported);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.d("Entzerren", "onSaveInstanceState...");
// Save the current state
savedInstanceState.putBoolean(SHOWHELPSAVED, tutorial);
savedInstanceState.putBoolean(IMAGEENTZERRT, entzerrt);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onBackPressed() {
if (loading_active)
return;
if (entzerrt) {
// ersetze das Bild mit dem Backup
File imageFile_bak = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAMEBAK));
File imageFile = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAME));
copy(imageFile_bak, imageFile);
// Bild in die View laden
loadImageFile();
entzerrungsView.showCorners(true);
entzerrungsView.calcCornersWithDetector();
entzerrt = false;
entzerrungsView.update();
}
else {
// wenn nicht, gehe zum vorherigen Screen zurück
// (nicht manuell die Start-Activity starten, einfach das onBackPressed gewöhnlich handlen lassen)
super.onBackPressed();
}
}
@Override
protected void onPause() {
super.onPause();
Log.d("Entzerrung", "onPause...");
// Gegebenenfalls laufende Drag-Operationen abbrechen
if (entzerrungsView.isCurrentlyDragging()) {
entzerrungsView.cancelAllDragging();
}
}
public void onClick_EntzerrungOk(View v) {
if (loading_active)
return;
if (tutorial) {
endQuickHelp();
return;
}
// wenn entzerrt werden soll...
if (entzerrungsView.isShowingCorners()) {
// Bildschirm sperren
lockScreenOrientation();
startLoadingScreen();
// Entzerrung in AsyncTask starten
new EntzerrenTask().execute();
}
else {
// temp_bak löschen
File imageFile_bak = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAMEBAK));
if (imageFile_bak.exists()) {
imageFile_bak.delete();
}
// Navigation mit Parameter loadmapid = -1 (== neue Karte) aufrufen
Intent intent_nav = new Intent(this, Navigation.class);
intent_nav.putExtra(Navigation.INTENT_LOADMAPID, -1L);
startActivity(intent_nav);
finish();
}
}
public void startLoadingScreen() {
if (!loading_active) {
loading_active = true;
getLayoutInflater().inflate(R.layout.entzerren_loading, layoutFrame);
}
}
public void endLoadingScreen() {
if (loading_active) {
loading_active = false;
layoutFrame.removeViewAt(layoutFrame.getChildCount() - 1);
}
}
public boolean isLoadingActive() {
return loading_active;
}
public void startQuickHelp() {
if (!tutorial) {
tutorial = true;
getLayoutInflater().inflate(R.layout.entzerren_help, layoutFrame);
}
}
public void endQuickHelp() {
if (tutorial) {
tutorial = false;
layoutFrame.removeViewAt(layoutFrame.getChildCount() - 1);
}
}
public boolean isInQuickHelp() {
return tutorial;
}
public void showErrorMessage(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
public void showErrorMessage(int resID) {
showErrorMessage(getResources().getString(resID));
}
public void lockScreenOrientation() {
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Configuration configuration = getResources().getConfiguration();
int rotation = windowManager.getDefaultDisplay().getRotation();
// Search for the natural position of the device
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
(rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) ||
configuration.orientation == Configuration.ORIENTATION_PORTRAIT &&
(rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270)) {
// Natural position is Landscape
switch (rotation) {
case Surface.ROTATION_0:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
case Surface.ROTATION_90:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
break;
case Surface.ROTATION_180:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
break;
case Surface.ROTATION_270:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
}
}
else {
// Natural position is Portrait
switch (rotation) {
case Surface.ROTATION_0:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
case Surface.ROTATION_90:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
case Surface.ROTATION_180:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
break;
case Surface.ROTATION_270:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
break;
}
}
}
public void unlockScreenOrientation()
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
public void loadImageFile() {
// Verwende statischen Dateinamen als Eingabe
File imageFile = new File(MapEverApp.getAbsoluteFilePath(INPUTFILENAME));
try {
// Bild in die View laden
entzerrungsView.loadImage(imageFile);
}
catch (FileNotFoundException e) {
showErrorMessage(R.string.error_filenotfound);
e.printStackTrace();
}
catch (OutOfMemoryError e) {
showErrorMessage(R.string.error_outofmemory);
e.printStackTrace();
}
}
// TODO mit neuem optimiertem Entzerrungsalgorithmus hinfällig...?
private boolean saveBitmap(Bitmap bitmap, String outFilename) {
File outFile = new File(MapEverApp.getAbsoluteFilePath(outFilename));
try {
// Outputstream öffnen
FileOutputStream outStream = new FileOutputStream(outFile);
// Bitmap komprimiert in Outputstream schreiben
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.close();
}
catch (IOException e) {
showErrorMessage(R.string.error_io);
e.printStackTrace();
return false;
}
return true;
}
public void copy(File src, File dst) {
try {
FileUtils.copyFileToFile(src, dst);
}
catch (IOException e) {
showErrorMessage(R.string.error_io);
e.printStackTrace();
}
}
private class EntzerrenTask extends AsyncTask<Void, Void, String> {
Bitmap entzerrtesBitmap = null;
@Override
protected String doInBackground(Void... params) {
String result = null;
try {
// TODO nach Möglichkeit ohne große Bitmaps (also streambasierte LargeImage-Version für JumbledImage)
// .... (auch saveBitmap ersetzen)
int sampleSize = 1;
// Beginne mit SampleSize 1 und skalier das Bild soweit runter, bis es nicht an einem OOM scheitert.
// TODO bessere Methode um initiale SampleSize zu bestimmen, um Zeit zu sparen?
// .... (eig. hinfällig mit optimiertem Algorithmus)
while (sampleSize <= 32) {
try {
// Punktkoordinaten als float[8] abrufen
float coordinates[] = entzerrungsView.getPointOffsets(sampleSize);
// Bitmap erzeugen
Bitmap sampledBitmap = entzerrungsView.getSampledBitmap(sampleSize);
if (sampledBitmap == null) {
Log.e("EntzerrenTask/doInBackground", "Decoding bitmap with SampleSize " + sampleSize + " resulted in null...");
sampleSize *= 2;
}
// Bitmap entzerren
entzerrtesBitmap = JumbledImage.transform(sampledBitmap, coordinates);
break;
}
catch (OutOfMemoryError e) {
// Noch mal mit doppelter SampleSize (halbiere bisherige Bildgröße) versuchen
sampleSize *= 2;
if (sampleSize > 32) {
// Exception weiterreichen
throw e;
}
}
}
if (entzerrtesBitmap == null) {
Log.e("EntzerrenTask/doInBackground", "Couldn't decode stream after " + sampleSize + " tries!");
}
else {
// entzerrtes Bild abspeichern
saveBitmap(entzerrtesBitmap, Entzerren.INPUTFILENAME);
}
}
catch (OutOfMemoryError e) {
result = getResources().getString(R.string.error_outofmemory);
e.printStackTrace();
}
catch (ArrayIndexOutOfBoundsException e) {
result = getResources().getString(R.string.deskewing_error_invalidcorners);
}
catch (IllegalArgumentException e) {
result = getResources().getString(R.string.deskewing_error_invalidcorners);
}
catch (NullPointerException e) {
// passiert z.B. bei unpassendem Dateiformat (GIF?)
// TODO irgendwas weiter machen? Entzerrung für GIFs von vornherein deaktivieren?
result = getResources().getString(R.string.deskewing_error_deskewfailure);
Log.e("EntzerrenTask/doInBackground", "NullPointerException while trying to deskew image");
e.printStackTrace();
}
return result;
}
@Override
protected void onPostExecute(String result) {
// execution of result of long time consuming operation
if (result != null) {
showErrorMessage(result);
}
else {
// entzerrtes Bild in die View laden
loadImageFile();
entzerrungsView.showCorners(false);
entzerrungsView.calcCornerDefaults();
entzerrt = true;
}
endLoadingScreen();
unlockScreenOrientation();
entzerrungsView.update();
}
}
}