/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import com.badlogic.gdx.math.Vector2; import com.google.common.io.Closeables; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.io.StorageHandler; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import ar.com.hjg.pngj.IImageLine; import ar.com.hjg.pngj.PngReader; import ar.com.hjg.pngj.PngWriter; import ar.com.hjg.pngj.PngjException; import ar.com.hjg.pngj.chunks.ChunkCopyBehaviour; import ar.com.hjg.pngj.chunks.ChunkHelper; import ar.com.hjg.pngj.chunks.PngChunk; import ar.com.hjg.pngj.chunks.PngChunkTextVar; public final class ImageEditing { public enum ResizeType { STRETCH_TO_RECTANGLE, STAY_IN_RECTANGLE_WITH_SAME_ASPECT_RATIO, FILL_RECTANGLE_WITH_SAME_ASPECT_RATIO } // Suppress default constructor for noninstantiability private ImageEditing() { throw new AssertionError(); } /** * Scales the bitmap to the specified size. * * @param bitmap the bitmap to resize * @param xSize desired x size * @param ySize desired y size * @return a new, scaled bitmap */ private static Bitmap scaleBitmap(Bitmap bitmap, int xSize, int ySize) { if (bitmap == null) { return null; } Matrix matrix = new Matrix(); float scaleWidth = (((float) xSize) / bitmap.getWidth()); float scaleHeight = (((float) ySize) / bitmap.getHeight()); matrix.postScale(scaleWidth, scaleHeight); Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return newBitmap; } public static Bitmap getScaledBitmapFromPath(String imagePath, int outputRectangleWidth, int outputRectangleHeight, ResizeType resizeType, boolean justScaleDown) { if (imagePath == null) { return null; } int[] imageDimensions = getImageDimensions(imagePath); int originalWidth = imageDimensions[0]; int originalHeight = imageDimensions[1]; int newWidth = originalHeight; int newHeight = originalWidth; int loadingSampleSize = 1; double sampleSizeWidth = ((double) originalWidth) / (double) outputRectangleWidth; double sampleSizeHeight = ((double) originalHeight) / (double) outputRectangleHeight; double sampleSizeMinimum = Math.min(sampleSizeWidth, sampleSizeHeight); double sampleSizeMaximum = Math.max(sampleSizeWidth, sampleSizeHeight); if (resizeType == ResizeType.STRETCH_TO_RECTANGLE) { newWidth = outputRectangleWidth; newHeight = outputRectangleHeight; } else if (resizeType == ResizeType.STAY_IN_RECTANGLE_WITH_SAME_ASPECT_RATIO) { newWidth = (int) Math.floor(originalWidth / sampleSizeMaximum); newHeight = (int) Math.floor(originalHeight / sampleSizeMaximum); } else if (resizeType == ResizeType.FILL_RECTANGLE_WITH_SAME_ASPECT_RATIO) { newWidth = (int) Math.floor(originalWidth / sampleSizeMinimum); newHeight = (int) Math.floor(originalHeight / sampleSizeMinimum); } loadingSampleSize = calculateInSampleSize(originalWidth, originalHeight, outputRectangleWidth, outputRectangleHeight); BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); bitmapOptions.inSampleSize = loadingSampleSize; bitmapOptions.inJustDecodeBounds = false; Bitmap tempBitmap = BitmapFactory.decodeFile(imagePath, bitmapOptions); return scaleBitmap(tempBitmap, newWidth, newHeight); } public static Bitmap getScaledBitmapOfLoadedBitmap(byte[] byteArray, int outputRectangleWidth, int outputRectangleHeight, ResizeType resizeType, boolean justScaleDown) { if (byteArray == null) { return null; } ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray); int[] imageDimensions = getImageDimensionsForLoadedImage(inputStream); Closeables.closeQuietly(inputStream); int originalWidth = imageDimensions[0]; int originalHeight = imageDimensions[1]; int newWidth = originalHeight; int newHeight = originalWidth; int loadingSampleSize = 1; double sampleSizeWidth = ((double) originalWidth) / (double) outputRectangleWidth; double sampleSizeHeight = ((double) originalHeight) / (double) outputRectangleHeight; double sampleSizeMinimum = Math.min(sampleSizeWidth, sampleSizeHeight); double sampleSizeMaximum = Math.max(sampleSizeWidth, sampleSizeHeight); if (resizeType == ResizeType.STRETCH_TO_RECTANGLE) { newWidth = outputRectangleWidth; newHeight = outputRectangleHeight; } else if (resizeType == ResizeType.STAY_IN_RECTANGLE_WITH_SAME_ASPECT_RATIO) { newWidth = (int) Math.floor(originalWidth / sampleSizeMaximum); newHeight = (int) Math.floor(originalHeight / sampleSizeMaximum); } else if (resizeType == ResizeType.FILL_RECTANGLE_WITH_SAME_ASPECT_RATIO) { newWidth = (int) Math.floor(originalWidth / sampleSizeMinimum); newHeight = (int) Math.floor(originalHeight / sampleSizeMinimum); } loadingSampleSize = calculateInSampleSize(originalWidth, originalHeight, outputRectangleWidth, outputRectangleHeight); BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); bitmapOptions.inSampleSize = loadingSampleSize; bitmapOptions.inJustDecodeBounds = false; inputStream = new ByteArrayInputStream(byteArray); Bitmap tempBitmap = BitmapFactory.decodeStream(inputStream, null, bitmapOptions); Closeables.closeQuietly(inputStream); return scaleBitmap(tempBitmap, newWidth, newHeight); } public static int[] getImageDimensionsForLoadedImage(InputStream inputStream) { int[] imageDimensions = new int[2]; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(inputStream, null, options); imageDimensions[0] = options.outWidth; imageDimensions[1] = options.outHeight; return imageDimensions; } public static int[] getImageDimensions(String imagePath) { int[] imageDimensions = new int[2]; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imagePath, options); imageDimensions[0] = options.outWidth; imageDimensions[1] = options.outHeight; return imageDimensions; } public static void overwriteImageFileWithNewBitmap(File imageFile) throws FileNotFoundException { BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap immutableBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); int bitmapWidth = immutableBitmap.getWidth(); int bitmapHeight = immutableBitmap.getHeight(); int[] bitmapPixels = new int[bitmapWidth * bitmapHeight]; immutableBitmap.getPixels(bitmapPixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight); Bitmap mutableBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); mutableBitmap.setPixels(bitmapPixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight); StorageHandler.saveBitmapToImageFile(imageFile, mutableBitmap); } public static Bitmap rotateBitmap(Bitmap bitmap, int rotationDegree) { Matrix rotateMatrix = new Matrix(); rotateMatrix.postRotate(rotationDegree); Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), rotateMatrix, true); return rotatedBitmap; } public static void scaleImageFile(File file, double scaleFactor) throws FileNotFoundException { String path = file.getAbsolutePath(); int[] originalBackgroundImageDimensions = getImageDimensions(path); Bitmap scaledBitmap = ImageEditing.getScaledBitmapFromPath(path, (int) (originalBackgroundImageDimensions[0] * scaleFactor), (int) (originalBackgroundImageDimensions[1] * scaleFactor), ImageEditing.ResizeType.FILL_RECTANGLE_WITH_SAME_ASPECT_RATIO, false); StorageHandler.saveBitmapToImageFile(file, scaledBitmap); } public static double calculateScaleFactorToScreenSize(int resourceId, Context context) { if (context.getResources().getResourceTypeName(resourceId).compareTo("drawable") == 0) { //AssetFileDescriptor file = context.getResources().openRawResourceFd(resourceId); //getImageDimensions(file); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), resourceId, options); return calculateScaleFactor(options.outWidth, options.outHeight, ScreenValues.SCREEN_WIDTH, ScreenValues.SCREEN_HEIGHT, true); } else { throw new IllegalArgumentException("resource is not an image"); } } private static double calculateScaleFactor(int originalWidth, int originalHeight, int newWidth, int newHeight, boolean fillOutWholeNewArea) { if (originalHeight == 0 || originalWidth == 0 || newHeight == 0 || newWidth == 0) { throw new IllegalArgumentException("One or more values are 0"); } double widthScaleFactor = ((double) newWidth) / ((double) originalWidth); double heightScaleFactor = ((double) newHeight) / ((double) originalHeight); if (fillOutWholeNewArea) { return Math.max(widthScaleFactor, heightScaleFactor); } else { return Math.min(widthScaleFactor, heightScaleFactor); } } public static Vector2 calculateScaleFactorsToScreenSize(int resourceId, Context context) { if (context.getResources().getResourceTypeName(resourceId).compareTo("drawable") == 0) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), resourceId, options); return calculateScaleFactors(options.outWidth, options.outHeight, ScreenValues.SCREEN_WIDTH, ScreenValues.SCREEN_HEIGHT); } else { throw new IllegalArgumentException("resource is not an image"); } } private static Vector2 calculateScaleFactors(int originalWidth, int originalHeight, int newWidth, int newHeight) { if (originalHeight == 0 || originalWidth == 0 || newHeight == 0 || newWidth == 0) { throw new IllegalArgumentException("One or more values are 0"); } double widthScaleFactor = ((double) newWidth) / ((double) originalWidth); double heightScaleFactor = ((double) newHeight) / ((double) originalHeight); return new Vector2((float) widthScaleFactor, (float) heightScaleFactor); } //method from developer.android.com public static int calculateInSampleSize(int origWidth, int origHeight, int reqWidth, int reqHeight) { // Raw height and width of image final int height = origHeight; final int width = origWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } public static String readMetaDataStringFromPNG(String absolutePath, String key) throws PngjException { File image = new File(absolutePath); PngReader pngr = new PngReader(image); pngr.readSkippingAllRows(); for (PngChunk c : pngr.getChunksList().getChunks()) { if (!ChunkHelper.isText(c)) { continue; } PngChunkTextVar ct = (PngChunkTextVar) c; String k = ct.getKey(); String val = ct.getVal(); if (key.equals(k)) { pngr.close(); return val; } } pngr.close(); return ""; } public static synchronized void writeMetaDataStringToPNG(String absolutePath, String key, String value) { String tempFilename = absolutePath.substring(0, absolutePath.length() - 4) + "___temp.png"; File oldFile = new File(absolutePath); File newFile = new File(tempFilename); PngReader pngr = new PngReader(oldFile); PngWriter pngw = new PngWriter(newFile, pngr.imgInfo, true); pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_ALL); pngw.getMetadata().setText(key, value); for (int row = 0; row < pngr.imgInfo.rows; row++) { IImageLine l1 = pngr.readRow(); pngw.writeRow(l1); } pngr.end(); pngw.end(); oldFile.delete(); newFile.renameTo(new File(absolutePath)); } }