diff options
Diffstat (limited to 'src/com/ruesga/android/wallpapers/photophase/utils')
5 files changed, 995 insertions, 0 deletions
diff --git a/src/com/ruesga/android/wallpapers/photophase/utils/BitmapUtils.java b/src/com/ruesga/android/wallpapers/photophase/utils/BitmapUtils.java new file mode 100644 index 0000000..4fb7257 --- /dev/null +++ b/src/com/ruesga/android/wallpapers/photophase/utils/BitmapUtils.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 Jorge Ruesga + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ruesga.android.wallpapers.photophase.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.graphics.Matrix; +import android.media.ExifInterface; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * A helper class for deal with Bitmaps + */ +public class BitmapUtils { + + /** + * Method that decodes a bitmap + * + * @param bitmap The bitmap buffer to decode + * @return Bitmap The decoded bitmap + */ + public static Bitmap decodeBitmap(InputStream bitmap) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferQualityOverSpeed = false; + options.inPreferredConfig = Bitmap.Config.RGB_565; + return BitmapFactory.decodeStream(bitmap, null, options); + } + + /** + * Method that decodes a bitmap + * + * @param file The bitmap file to decode + * @param reqWidth The request width + * @param reqHeight The request height + * @return Bitmap The decoded bitmap + */ + public static Bitmap decodeBitmap(File file, int reqWidth, int reqHeight) { + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + + // Calculate inSampleSize (use 1024 as maximum size, the minimum supported + // by all the gles20 devices) + options.inSampleSize = calculateBitmapRatio( + options, + Math.min(reqWidth, 1024), + Math.min(reqHeight, 1024)); + + // Decode the bitmap with inSampleSize set + options.inJustDecodeBounds = false; + options.inPreferQualityOverSpeed = false; + options.inPurgeable = true; + options.inInputShareable = true; + options.inDither = true; + Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + if (bitmap == null) { + return null; + } + + // Test if the bitmap has exif format, and decode properly + Bitmap out = decodeExifBitmap(file, bitmap); + if (!out.equals(bitmap)) { + bitmap.recycle(); + bitmap = null; + } + return out; + } + + /** + * Method that decodes an Exif bitmap + * + * @param file The file to decode + * @param src The bitmap reference + * @return Bitmap The decoded bitmap + */ + private static Bitmap decodeExifBitmap(File file, Bitmap src) { + try { + // Try to load the bitmap as a bitmap file + ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); + if (orientation == 0) { + return src; + } + Matrix matrix = new Matrix(); + if (orientation == 6) { + matrix.postRotate(90); + } else if (orientation == 3) { + matrix.postRotate(180); + } else if (orientation == 8) { + matrix.postRotate(270); + } + // Rotate the bitmap + return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); + } catch (IOException e) { + // Ignore + } + return src; + } + + /** + * Method that calculate the bitmap size prior to decode + * + * @param options The bitmap factory options + * @param reqWidth The request width + * @param reqHeight The request height + * @return int The picture ratio + */ + private static int calculateBitmapRatio(Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + // Calculate ratios of height and width to requested height and width + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + + // Choose the smallest ratio as inSampleSize value, this will guarantee + // a final image with both dimensions larger than or equal to the + // requested height and width. + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + } + + return inSampleSize; + } + +} diff --git a/src/com/ruesga/android/wallpapers/photophase/utils/DispositionUtil.java b/src/com/ruesga/android/wallpapers/photophase/utils/DispositionUtil.java new file mode 100644 index 0000000..9d6b383 --- /dev/null +++ b/src/com/ruesga/android/wallpapers/photophase/utils/DispositionUtil.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 Jorge Ruesga + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ruesga.android.wallpapers.photophase.utils; + +import android.graphics.Rect; + +import com.ruesga.android.wallpapers.photophase.model.Disposition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A helper class with disposition utils + */ +public final class DispositionUtil { + + /** + * Method that converts a disposition string to a disposition reference + * + * @param value The value to convert + * @return List<Disposition> The dispositions reference + */ + public static List<Disposition> toDispositions(String value) { + String[] v = value.split("\\|"); + List<Disposition> dispositions = new ArrayList<Disposition>(v.length); + for (String s : v) { + String[] s1 = s.split(":"); + String[] s2 = s1[0].split("x"); + String[] s3 = s1[1].split("x"); + Disposition disposition = new Disposition(); + disposition.x = Integer.parseInt(s2[0]); + disposition.y = Integer.parseInt(s2[1]); + disposition.w = Integer.parseInt(s3[0]) - disposition.x + 1; + disposition.h = Integer.parseInt(s3[1]) - disposition.y + 1; + dispositions.add(disposition); + } + Collections.sort(dispositions); + return dispositions; + } + + /** + * Method that converts a disposition reference to a disposition string + * + * @param dispositions The value to convert + * @return String The dispositions string + */ + public static String fromDispositions(List<Disposition> dispositions) { + Collections.sort(dispositions); + StringBuilder sb = new StringBuilder(); + int count = dispositions.size(); + for (int i = 0; i < count; i++) { + Disposition disposition = dispositions.get(i); + sb.append(disposition.x) + .append("x") + .append(disposition.y) + .append(":") + .append(disposition.x + disposition.w - 1) + .append("x") + .append(disposition.y + disposition.h - 1); + if (i < (count - 1)) { + sb.append("|"); + } + } + return sb.toString(); + } + + /** + * Method that transform the disposition to a byte matrix + * + * @param dispositions The + * @return byte[][] The boolean matrix of the disposition + */ + public static byte[][] toMatrix(List<Disposition> dispositions, int cols, int rows) { + byte[][] matrix = new byte[rows][cols]; + for (Disposition disposition : dispositions) { + int count = disposition.y + disposition.h; + for (int row = disposition.y; row < count; row++) { + int count2 = disposition.x + disposition.w; + for (int col = disposition.x; col < count2; col++) { + matrix[row][col] = 1; + } + } + } + return matrix; + } + + /** + * Method that returns a disposition from a {@link Rect} reference + * + * @return Disposition The disposition + */ + public static Disposition fromRect(Rect r) { + Disposition disposition = new Disposition(); + disposition.x = r.left; + disposition.y = r.top; + disposition.w = r.width(); + disposition.h = r.height(); + return disposition; + } +} diff --git a/src/com/ruesga/android/wallpapers/photophase/utils/GLESUtil.java b/src/com/ruesga/android/wallpapers/photophase/utils/GLESUtil.java new file mode 100644 index 0000000..51b7b74 --- /dev/null +++ b/src/com/ruesga/android/wallpapers/photophase/utils/GLESUtil.java @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2013 Jorge Ruesga + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ruesga.android.wallpapers.photophase.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Rect; +import android.media.effect.Effect; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + + +/** + * A helper class with some useful methods for deal with GLES. + */ +public final class GLESUtil { + + private static final String TAG = "GLESUtil"; + + private static final boolean DEBUG = false; + + private static final Object sSync = new Object(); + + /** + * A helper class to deal with OpenGL float colors. + */ + public static class GLColor { + + private static final float MAX_COLOR = 255.0f; + + /** + * Red + */ + public float r; + /** + * Green + */ + public float g; + /** + * Blue + */ + public float b; + /** + * Alpha + */ + public float a; + + /** + * Constructor of <code>GLColor</code> from ARGB + * + * @param a Alpha + * @param r Red + * @param g Green + * @param b Alpha + */ + public GLColor(int a, int r, int g, int b) { + this.a = a / MAX_COLOR; + this.r = r / MAX_COLOR; + this.g = g / MAX_COLOR; + this.b = b / MAX_COLOR; + } + + /** + * Constructor of <code>GLColor</code> from ARGB. + * + * @param argb An #AARRGGBB string + */ + public GLColor(String argb) { + int color = Color.parseColor(argb); + this.a = Color.alpha(color) / MAX_COLOR; + this.r = Color.red(color) / MAX_COLOR; + this.g = Color.green(color) / MAX_COLOR; + this.b = Color.blue(color) / MAX_COLOR; + } + + /** + * Constructor of <code>GLColor</code> from ARGB. + * + * @param argb An #AARRGGBB number + */ + public GLColor(int argb) { + this.a = Color.alpha(argb) / MAX_COLOR; + this.r = Color.red(argb) / MAX_COLOR; + this.g = Color.green(argb) / MAX_COLOR; + this.b = Color.blue(argb) / MAX_COLOR; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(a); + result = prime * result + Float.floatToIntBits(b); + result = prime * result + Float.floatToIntBits(g); + result = prime * result + Float.floatToIntBits(r); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GLColor other = (GLColor) obj; + if (Float.floatToIntBits(a) != Float.floatToIntBits(other.a)) + return false; + if (Float.floatToIntBits(b) != Float.floatToIntBits(other.b)) + return false; + if (Float.floatToIntBits(g) != Float.floatToIntBits(other.g)) + return false; + if (Float.floatToIntBits(r) != Float.floatToIntBits(other.r)) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "#"+Integer.toHexString(Color.argb((int)a, (int)r, (int)g, (int)b)); + } + } + + /** + * Class that holds some information about a GLES texture + */ + public static class GLESTextureInfo { + /** + * Handle of the texture + */ + public int handle = 0; + /** + * The bitmap reference + */ + public Bitmap bitmap; + /** + * The path to the texture + */ + public File path; + /** + * The effect to apply + */ + public Effect effect; + } + + /** + * Method that load a vertex shader and returns its handler identifier. + * + * @param src The source shader + * @return int The handler identifier of the shader + */ + public static int loadVertexShader(String src) { + return loadShader(src, GLES20.GL_VERTEX_SHADER); + } + + /** + * Method that load a fragment shader and returns its handler identifier. + * + * @param src The source shader + * @return int The handler identifier of the shader + */ + public static int loadFragmentShader(String src) { + return loadShader(src, GLES20.GL_FRAGMENT_SHADER); + } + + /** + * Method that load a shader and returns its handler identifier. + * + * @param src The source shader + * @param type The type of shader + * @return int The handler identifier of the shader + */ + public static int loadShader(String src, int type) { + int[] compiled = new int[1]; + // Create, load and compile the shader + int shader = GLES20.glCreateShader(type); + GLESUtil.glesCheckError("glCreateShader"); + if (shader <= 0) { + Log.e(TAG, "Cannot create a shader"); + return 0; + } + GLES20.glShaderSource(shader, src); + GLESUtil.glesCheckError("glShaderSource"); + GLES20.glCompileShader(shader); + GLESUtil.glesCheckError("glesCheckError"); + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + GLESUtil.glesCheckError("glesCheckError"); + if (compiled[0] <= 0) { + String msg = "Shader compilation error trace:\n" + GLES20.glGetShaderInfoLog(shader); + Log.e(TAG, msg); + return 0; + } + return shader; + } + + /** + * Method that create a new program from its shaders (vertex and fragment) + * + * @param res A resources reference + * @param vertexShaderId The vertex shader glsl resource + * @param fragmentShaderId The fragment shader glsl resource + * @return int The handler identifier of the program + */ + public static int createProgram(Resources res, int vertexShaderId, int fragmentShaderId) { + return createProgram( + readResource(res, vertexShaderId), + readResource(res, fragmentShaderId)); + } + + /** + * Method that create a new program from its shaders (vertex and fragment) + * + * @param vertexShaderSrc The vertex shader + * @param fragmentShaderSrc The fragment shader + * @return int The handler identifier of the program. + */ + public static int createProgram(String vertexShaderSrc, String fragmentShaderSrc) { + int vshader = 0; + int fshader = 0; + int progid = 0; + int[] link = new int[1]; + + try { + // Check that we have valid shaders + if (vertexShaderSrc == null || fragmentShaderSrc == null) { + return 0; + } + + // Load the vertex and fragment shaders + vshader = loadVertexShader(vertexShaderSrc); + fshader = loadFragmentShader(fragmentShaderSrc); + + // Create the programa ref + progid = GLES20.glCreateProgram(); + GLESUtil.glesCheckError("glCreateProgram"); + if (progid <= 0) { + String msg = "Cannot create a program"; + Log.e(TAG, msg); + return 0; + } + + // Attach the shaders + GLES20.glAttachShader(progid, vshader); + GLESUtil.glesCheckError("glAttachShader"); + GLES20.glAttachShader(progid, fshader); + GLESUtil.glesCheckError("glAttachShader"); + + // Link the program + GLES20.glLinkProgram(progid); + GLESUtil.glesCheckError("glLinkProgram"); + + GLES20.glGetProgramiv(progid, GLES20.GL_LINK_STATUS, link, 0); + GLESUtil.glesCheckError("glGetProgramiv"); + if (link[0] <= 0) { + String msg = "Program compilation error trace:\n" + GLES20.glGetProgramInfoLog(progid); + Log.e(TAG, msg); + return 0; + } + + // Return the program + return progid; + + } finally { + // Delete the shaders + if (vshader != 0) { + GLES20.glDeleteShader(vshader); + GLESUtil.glesCheckError("glDeleteShader"); + } + if (fshader != 0) { + GLES20.glDeleteShader(fshader); + GLESUtil.glesCheckError("glDeleteShader"); + } + } + } + + /** + * Method that loads a texture from a file. + * + * @param file The image file + * @param dimensions The desired dimensions + * @param effect The effect to apply to the image or null if no effect is needed + * @param dimen The new dimensions + * @param recycle If the bitmap should be recycled + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture( + File file, Rect dimensions, Effect effect, Rect dimen, boolean recycle) { + Bitmap bitmap = null; + try { + // Decode and associate the bitmap (invert the desired dimensions) + bitmap = BitmapUtils.decodeBitmap(file, dimensions.height(), dimensions.width()); + if (bitmap == null) { + Log.e(TAG, "Failed to decode the file bitmap"); + return new GLESTextureInfo(); + } + + if (DEBUG) Log.d(TAG, "image: " + file.getAbsolutePath()); + GLESTextureInfo ti = loadTexture(bitmap, effect, dimen); + ti.path = file; + return ti; + + } catch (Exception e) { + String msg = "Failed to generate a valid texture from file: " + file.getAbsolutePath(); + Log.e(TAG, msg, e); + return new GLESTextureInfo(); + + } finally { + // Recycle the bitmap + if (bitmap != null && recycle) { + bitmap.recycle(); + bitmap = null; + } + } + } + + /** + * Method that loads a texture from a resource context. + * + * @param ctx The current context + * @param resourceId The resource identifier + * @param effect The effect to apply to the image or null if no effect is needed + * @param dimen The new dimensions + * @param recycle If the bitmap should be recycled + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture( + Context ctx, int resourceId, Effect effect, Rect dimen, boolean recycle) { + Bitmap bitmap = null; + InputStream raw = null; + try { + // Decode and associate the bitmap + raw = ctx.getResources().openRawResource(resourceId); + bitmap = BitmapUtils.decodeBitmap(raw); + if (bitmap == null) { + String msg = "Failed to decode the resource bitmap"; + Log.e(TAG, msg); + return new GLESTextureInfo(); + } + + if (DEBUG) Log.d(TAG, "resourceId: " + resourceId); + GLESTextureInfo ti = loadTexture(bitmap, effect, dimen); + return ti; + + } catch (Exception e) { + String msg = "Failed to generate a valid texture from resource: " + resourceId; + Log.e(TAG, msg, e); + return new GLESTextureInfo(); + + } finally { + // Close the buffer + try { + if (raw != null) { + raw.close(); + } + } catch (IOException e) { + // Ignore. + } + // Recycle the bitmap + if (bitmap != null && recycle) { + bitmap.recycle(); + bitmap = null; + } + } + } + + /** + * Method that loads texture from a bitmap reference. + * + * @param bitmap The bitmap reference + * @param effect The effect to apply to the image or null if no effect is needed + * @param dimen The new dimensions + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture(Bitmap bitmap, Effect effect, Rect dimen) { + // Check that we have a valid image name reference + if (bitmap == null) { + return new GLESTextureInfo(); + } + + int num = effect == null ? 1 : 2; + + int[] textureHandles = new int[num]; + GLES20.glGenTextures(num, textureHandles, 0); + GLESUtil.glesCheckError("glGenTextures"); + if (textureHandles[0] <= 0 || (effect != null && textureHandles[1] <= 0)) { + Log.e(TAG, "Failed to generate a valid texture"); + return new GLESTextureInfo(); + } + + // Bind the texture to the name + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandles[0]); + GLESUtil.glesCheckError("glBindTexture"); + + // Set the texture properties + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLESUtil.glesCheckError("glTexParameteri"); + + // Load the texture + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + if (!GLES20.glIsTexture(textureHandles[0])) { + Log.e(TAG, "Failed to load a valid texture"); + return new GLESTextureInfo(); + } + + // Has a effect? + int handle = textureHandles[0]; + if (effect != null) { + // Apply the effect (we need a thread-safe call here) + synchronized (sSync) { + // No more than 1024 (the minimum supported by all the gles20 devices) + int w = Math.min(dimen.width(), 1024); + int h = Math.min(dimen.width(), 1024); + effect.apply(textureHandles[0], w, h, textureHandles[1]); + } + handle = textureHandles[1]; + + // Delete the unused texture + int[] textures = {textureHandles[0]}; + GLES20.glDeleteTextures(1, textures, 0); + GLESUtil.glesCheckError("glDeleteTextures"); + } + + // Return the texture handle identifier and the associated info + GLESTextureInfo ti = new GLESTextureInfo(); + ti.handle = handle; + ti.bitmap = bitmap; + ti.path = null; + return ti; + } + + /** + * Method that checks if an GLES error is present + * + * @param func The GLES function to check + * @return boolean If there was an error + */ + public static boolean glesCheckError(String func) { + int error = GLES20.glGetError(); + if (error != 0) { + Log.e(TAG, "GLES20 Error (" + glesGetErrorModule() + ") (" + func + "): " + + GLUtils.getEGLErrorString(error)); + return true; + } + return false; + } + + /** + * Method that returns the line and module that generates the current error + * + * @return String The line and module + */ + private static String glesGetErrorModule() { + try { + return String.valueOf(Thread.currentThread().getStackTrace()[4]); + } catch (IndexOutOfBoundsException ioobEx) { + // Ignore + } + return ""; + } + + /** + * Method that read a resource. + * + * @param res The resources reference + * @param resId The resource identifier + * @return String The shader source + * @throws IOException If an error occurs while loading the resource + */ + private static String readResource(Resources res, int resId) { + Reader reader = new InputStreamReader(res.openRawResource(resId)); + try { + final int BUFFER = 1024; + char[] data = new char[BUFFER]; + int read = 0; + StringBuilder sb = new StringBuilder(); + while ((read = reader.read(data, 0, BUFFER)) != -1) { + sb.append(data, 0, read); + } + return sb.toString(); + } catch (Exception e) { + Log.e(TAG, "Failed to read the resource " + resId); + return null; + } finally { + try { + reader.close(); + } catch (Exception ex) { + // Ignore + } + } + } + +} diff --git a/src/com/ruesga/android/wallpapers/photophase/utils/MERAlgorithm.java b/src/com/ruesga/android/wallpapers/photophase/utils/MERAlgorithm.java new file mode 100644 index 0000000..c7009c3 --- /dev/null +++ b/src/com/ruesga/android/wallpapers/photophase/utils/MERAlgorithm.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 Jorge Ruesga + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ruesga.android.wallpapers.photophase.utils; + +import android.graphics.Rect; +import java.util.Stack; + +/** + * The maximal empty rectangle algorithm that allows to find the rectangle with the maximal + * area that could be create in empty areas (in this case 0 in a byte matrix) + */ +// +// Based on the source discussed at http://discuss.leetcode.com/questions/260/maximal-rectangle +// +public final class MERAlgorithm { + + /** + * Method that returns the maximal empty rectangle (MER) for a matrix of bytes (1/0) + * + * @param matrix The matrix + * @return Rect The maximal empty rectangle + */ + public static Rect getMaximalEmptyRectangle(byte[][] matrix) { + // Check matrix + int rows = matrix.length; + if (rows == 0) return null; + + // Convert to histogram + int[][] histogram = toHistogram(matrix); + + // Find the maximal area of every histogram + Rect maxRect = new Rect(); + for (int i = 0; i < rows; ++i) { + Rect rect = maximalRectangle(histogram[i], i); + if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) { + maxRect = rect; + } + } + return maxRect; + } + + /** + * Method that returns the maximal rectangle for an histogram of areas + * + * @return Rect The maximal rectangle histogram/area + */ + @SuppressWarnings("boxing") + private static Rect maximalRectangle(int[] histogram, int row) { + Stack<Integer> stack = new Stack<Integer>(); + int length = histogram.length; + Rect maxRect = new Rect(); + int i = 0; + while (i < length) { + if (stack.isEmpty() || histogram[i] >= histogram[stack.peek()]) { + stack.push(i++); + } else { + Rect rect = new Rect(); + rect.left = stack.pop(); + rect.right = rect.left + (stack.isEmpty() ? i : (i - stack.peek() - 1)); + rect.top = row - histogram[rect.left] + 1; + rect.bottom = rect.top + histogram[rect.left]; + if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) { + maxRect = rect; + } + } + } + while (!stack.isEmpty()) { + Rect rect = new Rect(); + rect.left = stack.pop(); + rect.right = rect.left + (stack.isEmpty() ? i : (i - stack.peek() - 1)); + rect.top = row - histogram[rect.left] + 1; + rect.bottom = rect.top + histogram[rect.left]; + if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) { + maxRect = rect; + } + } + return maxRect; + } + + /** + * Method that converts the empty areas to a histogram + * + * @param matrix The matrix where to find the MER + * return int[][] The histogram of empty areas + */ + private static int[][] toHistogram(byte[][] matrix) { + int rows = matrix.length; + int cols = matrix[0].length; + int[][] histogram = new int[rows][cols]; + for (int h=0; h < cols; h++) { + if (matrix[0][h] == 0) { + histogram[0][h] = 1; + } + } + for (int w=1; w < rows; w++) { + for (int h=0; h < cols; h++) { + if (matrix[w][h] == 1) { + continue; + } + histogram[w][h] = histogram[w-1][h] + 1; + } + } + return histogram; + } +} diff --git a/src/com/ruesga/android/wallpapers/photophase/utils/Utils.java b/src/com/ruesga/android/wallpapers/photophase/utils/Utils.java new file mode 100644 index 0000000..296b96a --- /dev/null +++ b/src/com/ruesga/android/wallpapers/photophase/utils/Utils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013 Jorge Ruesga + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ruesga.android.wallpapers.photophase.utils; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.RectF; +import android.util.DisplayMetrics; + +import java.util.Random; + +/** + * A helper class with utilities + */ +public class Utils { + + private static Random sRandom = new Random(); + + /** + * This method converts dp unit to equivalent device specific value in pixels. + * + * @param ctx The current context + * @param dp A value in dp (Device independent pixels) unit + * @return float A float value to represent Pixels equivalent to dp according to device + */ + public static float convertDpToPixel(Context ctx, float dp) { + Resources resources = ctx.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * (metrics.densityDpi / 160f); + } + + /** + * Used to determine if the device is a tablet or not + * + * @param context The {@link Context} to use. + * @return True if the device is a tablet, false otherwise. + */ + public static final boolean isTablet(final Context context) { + final int layout = context.getResources().getConfiguration().screenLayout; + return (layout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } + + /** + * Method that converts a rect from a vertex data + * + * @param vertex The vertex array + * @return RectF The rect data + */ + public static RectF rectFromVertex(float[] vertex) { + RectF rect = new RectF(); + rect.left = vertex[0]; + rect.top = vertex[7]; + rect.right = vertex[6]; + rect.bottom = vertex[1]; + return rect; + } + + /** + * Method that returns a random number between two numbers. + * + * @param low The low number + * @param high The high number + * @return int The random number + */ + public static int getNextRandom(int low, int high) { + return low + (int)(sRandom.nextDouble() * ((high - low) + 1)); + } +}
\ No newline at end of file |