/* Copyright (C) 2014,2015 Philipp Lenk, Jan Müller
*
* 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.graphics.Bitmap;
import android.graphics.Matrix;
import android.util.Log;
public class JumbledImage
{
private JumbledImage() {
}
/**
* Given a distorted image and four corners marking the "intersting" segment, creates a corrected version
* of that segment and returns it.
*
* This may take some time and should therefore be started asynchronous if possible ;-)
*
* Currently the image size has to be chosen very carefully, because the way this is written requires
* Heap Memory for 2 full copies. I am working on a more memory saving version that does some caching
* to disk, but its currently unbearably slow (Mostly due to Bitmaps getPixel.
* The moment i use getPixels ram usage doubles...(Oh, and i have a slow bilinear filtering version, too)).
* Would anyone object to a JNI solution(Let me code C++, pretty please with sugar on top?)?
* Whom am i kidding, no one will read this anyway...
*
* @param jumbled A Bitmap containing the jumbled fragment. Beware, this will be destroyed!
* @param corners The four corners as a one dimensional array with coordinates in the following order:
* {x0,y0,x1,y1,x2,y2,x3,y3}
* @return A Bitmap containing the corrected segment
**/
public static Bitmap transform(Bitmap jumbled, float corners[])
{
Log.d("BITMAP", "" + jumbled.getWidth() + " " + jumbled.getHeight());
Log.d("FLOAT", "pre sort: " + corners[0] + " " + corners[1] + " " + corners[2] + " " + corners[3] + " " + corners[4] + " " + corners[5] + " " + corners[6] + " " + corners[7]);
// Sort corners clockwise
sort_corners(corners);
Log.d("FLOAT", "post sort: " + corners[0] + " " + corners[1] + " " + corners[2] + " " + corners[3] + " " + corners[4] + " " + corners[5] + " " + corners[6] + " " + corners[7]);
//The final image size is average(width) X average(height) of the original image
int dest_width = (int)(
Math.sqrt((corners[0]-corners[2])*(corners[0]-corners[2]) + (corners[1]-corners[3])*(corners[1]-corners[3]))+ //top length
Math.sqrt((corners[6]-corners[4])*(corners[6]-corners[4]) + (corners[7]-corners[5])*(corners[7]-corners[5])) //bottom length
)/2;
int dest_height = (int)(
Math.sqrt((corners[0]-corners[6])*(corners[0]-corners[6]) + (corners[1]-corners[7])*(corners[1]-corners[7]))+ //left length
Math.sqrt((corners[2]-corners[4])*(corners[2]-corners[4]) + (corners[3]-corners[5])*(corners[3]-corners[5])) //right length
)/2;
float mapped_corners[] =
{
0, 0,
dest_width, 0,
dest_width, dest_height,
0, dest_height
};
Matrix m = new Matrix();
m.setPolyToPoly(mapped_corners, 0, corners, 0, 4);
// get the image pixels as an array for faster processing
int src_pixels[] = new int[jumbled.getWidth() * jumbled.getHeight()];
int src_width = jumbled.getWidth();
jumbled.getPixels(src_pixels, 0, jumbled.getWidth(), 0, 0, jumbled.getWidth(), jumbled.getHeight());
Bitmap.Config conf = jumbled.getConfig();
jumbled = null;
int pixels[] = new int[dest_width * dest_height];
float point[] = new float[2];
for (int y = 0; y < dest_height; ++y)
{
for (int x = 0; x < dest_width; ++x)
{
point[0] = x;
point[1] = y;
m.mapPoints(point);
pixels[y * dest_width + x] = computeColor(src_pixels, src_width, point);
}
}
src_pixels = null;
return Bitmap.createBitmap(pixels, dest_width, dest_height, conf);
// return null;
}
private static int computeColor(int src[], int stride, float pos[])
{
// trivial, not-interpolated one:
return src[(int) pos[0] + stride * (int) pos[1]];
}
public static void sort_corners(float unsorted[])
{
assert (unsorted.length == 8);
float center[] = new float[2];
center[0] = (unsorted[0] + unsorted[2] + unsorted[4] + unsorted[6]) / 4;
center[1] = (unsorted[1] + unsorted[3] + unsorted[5] + unsorted[7]) / 4;
// clockwise bubble sort, yeahy ;-)
boolean swapped;
do
{
swapped = false;
for (int i = 0; i < 3; ++i)
{
if (!is_clockwise_turn(
new float[] { unsorted[2 * i], unsorted[2 * i + 1] },
new float[] { unsorted[2 * i + 2], unsorted[2 * i + 3] },
center))
{
float tmp = unsorted[2 * i];
unsorted[2 * i] = unsorted[2 * i + 2];
unsorted[2 * i + 2] = tmp;
tmp = unsorted[2 * i + 1];
unsorted[2 * i + 1] = unsorted[2 * i + 3];
unsorted[2 * i + 3] = tmp;
swapped = true;
}
}
} while (swapped);
}
private static boolean is_clockwise_turn(float first[], float second[], float center[])
{
assert ((first.length == second.length) && (second.length == center.length) && (center.length == 2));
return (first[0] - center[0]) * (second[1] - center[1]) - (second[0] - center[0]) * (first[1] - center[1]) > 0;
}
}