I know I can get it with Unity with these lines:
Texture2D texture = Resources.Load ("TextureTest") as Texture2D;
GameObject obPlane = GameObject.Find("Plane");
obPlane.renderer.material.mainTexture = texture ;
But it's not my goal. I want to do it from Android to Unity.
I've tried (in Java):
private boolean updateTexture(int IDTexture) {
Bitmap b = getBitMapFromFile("sample.bmp");
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, IDTexture);;
ByteBuffer bf = extract(b);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 256, 256, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bf);
return true;
}
And in Unity I get the textureID with cTexture.GetNativeTextureID():
void Start () {
cTexture = new Texture2D (width, height,TextureFormat.RGBA32,false);
cTexture.Apply();
obPlane.renderer.material.mainTexture = cTexture;
}
if(GUI.Button(new Rect(a,b,c,c), "Button")) {
activity.Call("updateTexture", cTexture.GetNativeTextureID());
}
But when I press the button, nothing happens.
A few things to remember when doing texturing with plugins:
Any time something alters Unity's graphical state externally, these alterations must be performed from the same thread as the renderer runs on. This means that your calling function should originate in one of the graphical Monobehaviour methods such as OnPreRender() and OnPreCull() to name but a few. I cant find an explicit list in the docs, but it should be easy enough to work out which are relevant. MonoBehaviour Docs The script that these calls are performed from must also be attached to the main camera in the scene.
Any time the graphical state of the engine is altered externally (like you are doing here) you must call GL.InvalidateState() afterwards. The docs explain this well:
"This invalidates any cached renderstates tied to the GL context. If for example a (native) plugin alters the renderstate settings then Unity's rendering architecture must be made aware of that to not assume the GL context is preserved." - GL.InvaliateState Docs
Slightly off topic
Let me also add that I attempted something identical to you, and failed to get the texture to update at all. While I'm not sure why this is the case, i can provide a much less efficient, but working method if speed is not critical. If you do get this working, please let me know on the question I've asked here, I'll be sure to update this answer if i find a proper solution.
This method will take in a bitmap from a path, scale and/or compress it (Unity has a max texture size of 4096x4096) into a byte array, which Unity then reads into a texture.
byte[] m_image;
File inFile = new File(_fileName);
Bitmap bm;
int width, height;
height = width = 4096;
String logTag = "BitmapHandler";
//Use BitmapFactory to check that bitmap is not too large.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bm = BitmapFactory.decodeFile(inFile.getAbsolutePath(), options);
m_StartWidth = options.outWidth;
m_StartHeight = options.outHeight;
//If it is, calculate a sampleSize to reduce the image by
if (options.outWidth > 4096 || options.outHeight > 4096) {
options.inSampleSize = calculateInSampleSize(options, 4096, 4096);
}
else {
width = options.outWidth;
height = options.outHeight;
options.inSampleSize = 1;
}
options.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(_fileName, options);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, out);
m_image = out.toByteArray();
m_width = bm.getWidth();
m_height = bm.getHeight();
out.flush();
out.close();
}
catch (Exception e) {
m_image = null;
m_height = -1;
m_width = -1;
return null;
}
return m_image;
How sample size is worked out - scales the image by a power of two so that both width and height are smaller than 4096 pixels:
private int calculateInSampleSize(BitmapFactory.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) {
while (height / inSampleSize > reqHeight || width / inSampleSize > reqWidth) {
inSampleSize *=2;
}
}
return inSampleSize;
}
I'm sorry that I can't directly solve the problem, but i hope that the info I've given will be of use to you!
Related
I am trying to reduce the size of the bitmap after taking the picture. I am able to reduce the size to max 900kb but I wan to reduce it further as much as possible
First I do this:
public static Bitmap decodeSampledBitmapFromResource(byte[] data,
int reqWidth, int reqHeight) {
//reqWidth is 320
//reqHeight is 480
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
then this
bitmap.compress(Bitmap.CompressFormat.PNG, 10, fos);
How do i compress the bitmap further ? or
Should I compress the byte[] instead?
I just want to send documents, black and white.
I know that the question has some time, but I think this answer could help someone.
There are three methods to compress your picture (the "byte[] data" you have recieved from the Camera):
1) PNG format -> It's the worst choice when the image has a lot of different colors, and in general, this is the case when you take a photo. The PNG format is more useful in images like logotypes and stuff like this.
The code:
String filename = "" //THE PATH WHERE YOU WANT TO PUT YOUR IMAGE
File pictureFile = new File(filename);
try{ //"data" is your "byte[] data"
FileOutputStream fos = new FileOutputStream(pictureFile);
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); //100-best quality
fos.write(data);
fos.close();
} catch (Exception e){
Log.d("Error", "Image "+filename+" not saved: " + e.getMessage());
}
2) JPG format -> It's a better option with an image with a lot of colors (the algoritm to compress is very different than PNG), but it does not have the 'alpha channel' that could be necesary in some scenarios.
There is only one change from the previous code:
bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); //100-best quality
3) WebP format -> It's the best format by far. An image of 1.0 MB in PNG could be compress to 100-200KB in JPG, and only to 30-50 KB in WebP. This format has also the 'alpha channel'. But it's not perfect: some browsers are not compatible with it. So you have to be careful about this in your project.
There is only one change from the previous code:
bmp.compress(Bitmap.CompressFormat.WEBP, 100, fos); //100-best quality
NOTES:
1) If this level of compress is not enought, you can play with the quality of the compress (the '100' that you can see in the bmp.compress() method). Be careful with the quality in the JPEG, because in some cases with a quality of 80%, you can see the impact in the image easyly. In PNG and WebP formats the loss percentage has less impact.
2) The other way to reduce the size is to resize the image. The easiest way I found is to simply create a smaller bitmap through Bitmap.createScaledBitmap method (more info).
3) And the last one method is to put the image in black and white. You can do this with this method:
Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
PS 1 - If someone wants to have a deeper knowledge about the image compress methods in Android, I recommend this talk by Colt McAnlis in the Google I/O 2016 (link).
You should use jpg instead of png if the alpha channel is not needed.
Also you can change the color config of your output bitmap:
// Decode bitmap with inSampleSize set
options.inPreferredConfig= Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
I'm facing a crash every time with a Galaxy S5 when trying to show a background image.
This background is located in xxhdpi resource folder, the size is the same as the S5 screen (1080x1920) so I don't need to call "createScaledBitmap" for scaling it. The resolution of this image is JPG 96dpi.
And when calling decodeResource... crash!!! How is this possible? Is the only bitmap I'm loading in this "super-powerful" device.
Thanks!!!
Below my code (scale = 1 for S5):
public static Bitmap decodeBitmapFromResource(Resources res, int resId, float scale) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options,
(int)(options.outWidth*scale),
(int)(options.outHeight*scale));
options.inJustDecodeBounds = false;
if (scale > 1) {
Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
return Bitmap.createScaledBitmap(bitmap, (int)(options.outWidth*scale),
(int)(options.outHeight*scale), true);
}
return BitmapFactory.decodeResource(res, resId, options);
}
i too faced this problem many times...
try using this code..
private Bitmap decodeFile(File f) throws IOException {
Bitmap b = null;
DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay()
.getMetrics(metrics);
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inDither = false; // Disable Dithering mode
o.inPurgeable = true; // Tell to gc that whether it needs free memory,
// the Bitmap can be cleared
o.inInputShareable = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > metrics.heightPixels
|| o.outWidth > metrics.widthPixels) {
scale = (int) Math.pow(
2,
(int) Math.ceil(Math.log(metrics.heightPixels
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
and take care of few things like make every bitmap null after its use etc.
try this
public static Bitmap decodeBitmapFromResource(String pathName, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// return BitmapFactory.decodeResource(res, resId, options);
return BitmapFactory.decodeFile(pathName, options);
}
public static int calculateInSampleSize(BitmapFactory.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;
}
Add this line in your Manifest file in the application tag. It doesn't solve the problem just allows your app to have more memory:
android:largeHeap="true"
UPDATE:
However using largeHeap is not a good solution. here is the google's doc about this.
However, the ability to request a large heap is intended only for a
small set of apps that can justify the need to consume more RAM (such
as a large photo editing app). Never request a large heap simply
because you've run out of memory and you need a quick fix—you should
use it only when you know exactly where all your memory is being
allocated and why it must be retained. Yet, even when you're confident
your app can justify the large heap, you should avoid requesting it to
whatever extent possible. Using the extra memory will increasingly be
to the detriment of the overall user experience because garbage
collection will take longer and system performance may be slower when
task switching or performing other common operations.
And about loading bitmaps:
When you load a bitmap, keep it in RAM only at the resolution you need
for the current device's screen, scaling it down if the original
bitmap is a higher resolution. Keep in mind that an increase in bitmap
resolution results in a corresponding (increase2) in memory needed,
because both the X and Y dimensions increase.
It's not bad to take a look at this page, it explains ways of managing memory:
How Your App Should Manage Memory
So I think my last answer is not a good solution and You might rethink your strategy in loading images. Hope this answer helps you ;)
I am processing up to 1200 images. I optimized it to work from 100 images up to 500 with the help of previous questions found here. Now, this is what I have:
public Bitmap getBitmap(String filepath) {
boolean done = false;
int downsampleBy = 2;
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
while (!done) {
options.inSampleSize = downsampleBy++;
try {
bitmap = BitmapFactory.decodeFile(filepath, options);
done = true;
} catch (OutOfMemoryError e) {
// Ignore. Try again.
}
}
return bitmap;
}
This function is called in a loop, and it goes really fast until it hits the 500th image. At this point it slows down, until it finally stops working at around the 600th image.
At this point I don't know how else to optimize it to make it work. What do you think is happening and how can I fix it?
EDIT
// Decode BItmap considering memory limitations
public Bitmap getBitmap(String filepath) {
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
options.inSampleSize= calculateInSampleSize(options, 160, 120);
return bitmap = BitmapFactory.decodeFile(filepath, options);
}
public static int calculateInSampleSize(BitmapFactory.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) {
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;
}
Made the changes of the accepted answer. Using the function from Google's tutorials to get the correct sample size. Added largeHeap in the manifest and only calling System.gc() once before I loop through all the images.
First of all, you should never expect to catch an Error. Described here: Java documentation An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
There is some help about loading bitmaps: Android Developers | Loading large bitmaps
You can get some more memory by declaring the largeHeap="true" attribute in your Application Manifest.
And also the System.gc() call might help freeing some unused memory, but I won't really rely on that call.
First off, I have read many posts and articles about out of memory exceptions but none of them have helped with my situation. What I'm trying to do is load an image from the sd card but scale it to an exact pixel size.
I first get the width and height of the image and calculate the sample size:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(backgroundPath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, getWidth(), getHeight());
Here's how I get the sample size (although its not really relevant):
public static int calculateInSampleSize(BitmapFactory.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;
// NOTE: we could use Math.floor here for potential better image quality
// however, this also results in more out of memory issues
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
Now that I have a sample size I load the image from disk to an approximate size (sample size):
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPurgeable = true;
Bitmap bmp = BitmapFactory.decodeFile(backgroundPath, options);
Now, I scale this bitmap that I have created to the exact size I need and clean up:
// scale the bitmap to the exact size we need
Bitmap editedBmp = Bitmap.createScaledBitmap(bmp, (int) (width * scaleFactor), (int) (height * scaleFactor), true);
// clean up first bitmap
bmp.recycle();
bmp = null;
System.gc(); // I know you shouldnt do this, but I'm desperate
The above step is usually get my out of memory exception. Does anyone know a way to load an exact size bitmap from disk to avoid having to create two separate bitmaps like above?
Also, it seems like more exceptions occur when the user runs this code for a second time (sets a new image). However, I make sure to unload the drawable that was created from the bitmap which allows it to be garbage collected before this code is run again.
Any suggestions?
Thanks,
Nick
In your case there's no need to create the intermediate bitmap after you've performed the first decode. Since you're drawing to to a Canvas, you can use either the following methods (whichever you find most convenient) to scale the image to the perfect size.
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
Maybe this method would be helpful, I think I pulled it off of stackoverflow myself. It solved my out of memory exception issue.
private Bitmap decodeFile(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=250;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
I have a large sized image. At runtime, I want to read the image from storage and scale it so that its weight and size gets reduced and I can use it as a thumbnail. When a user clicks on the thumbnail, I want to display the full-sized image.
Try this
Bitmap ThumbImage = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(imagePath), THUMBSIZE, THUMBSIZE);
This Utility is available from API_LEVEl 8. [Source]
My Solution
byte[] imageData = null;
try
{
final int THUMBNAIL_SIZE = 64;
FileInputStream fis = new FileInputStream(fileName);
Bitmap imageBitmap = BitmapFactory.decodeStream(fis);
imageBitmap = Bitmap.createScaledBitmap(imageBitmap, THUMBNAIL_SIZE, THUMBNAIL_SIZE, false);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
imageData = baos.toByteArray();
}
catch(Exception ex) {
}
The best solution I found is the following. Compared with the other solutions this one does not need to load the full image for creating a thumbnail, so it is more efficient!
Its limit is that you can not have a thumbnail with exact width and height but the solution as near as possible.
File file = ...; // the image file
Options bitmapOptions = new Options();
bitmapOptions.inJustDecodeBounds = true; // obtain the size of the image, without loading it in memory
BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOptions);
// find the best scaling factor for the desired dimensions
int desiredWidth = 400;
int desiredHeight = 300;
float widthScale = (float)bitmapOptions.outWidth/desiredWidth;
float heightScale = (float)bitmapOptions.outHeight/desiredHeight;
float scale = Math.min(widthScale, heightScale);
int sampleSize = 1;
while (sampleSize < scale) {
sampleSize *= 2;
}
bitmapOptions.inSampleSize = sampleSize; // this value must be a power of 2,
// this is why you can not have an image scaled as you would like
bitmapOptions.inJustDecodeBounds = false; // now we want to load the image
// Let's load just the part of the image necessary for creating the thumbnail, not the whole image
Bitmap thumbnail = BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOptions);
// Save the thumbnail
File thumbnailFile = ...;
FileOutputStream fos = new FileOutputStream(thumbnailFile);
thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.flush();
fos.close();
// Use the thumbail on an ImageView or recycle it!
thumbnail.recycle();
Here is a more complete solution to scaling down a Bitmap to thumbnail size. It expands on the Bitmap.createScaledBitmap solution by maintaining the aspect ratio of the images and also padding them to the same width so that they look good in a ListView.
Also, it would be best to do this scaling once and store the resulting Bitmap as a blob in your Sqlite database. I have included a snippet on how to convert the Bitmap to a byte array for this purpose.
public static final int THUMBNAIL_HEIGHT = 48;
public static final int THUMBNAIL_WIDTH = 66;
imageBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
Float width = new Float(imageBitmap.getWidth());
Float height = new Float(imageBitmap.getHeight());
Float ratio = width/height;
imageBitmap = Bitmap.createScaledBitmap(imageBitmap, (int)(THUMBNAIL_HEIGHT*ratio), THUMBNAIL_HEIGHT, false);
int padding = (THUMBNAIL_WIDTH - imageBitmap.getWidth())/2;
imageView.setPadding(padding, 0, padding, 0);
imageView.setImageBitmap(imageBitmap);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] byteArray = baos.toByteArray();
Use BitmapFactory.decodeFile(...) to get your Bitmap object and set it to an ImageView with ImageView.setImageBitmap().
On the ImageView set the layout dimensions to something small, eg:
android:layout_width="66dip" android:layout_height="48dip"
Add an onClickListener to the ImageView and launch a new activity, where you display the image in full size with
android:layout_width="wrap_content" android:layout_height="wrap_content"
or specify some larger size.
/**
* Creates a centered bitmap of the desired size.
*
* #param source original bitmap source
* #param width targeted width
* #param height targeted height
* #param options options used during thumbnail extraction
*/
public static Bitmap extractThumbnail(
Bitmap source, int width, int height, int options) {
if (source == null) {
return null;
}
float scale;
if (source.getWidth() < source.getHeight()) {
scale = width / (float) source.getWidth();
} else {
scale = height / (float) source.getHeight();
}
Matrix matrix = new Matrix();
matrix.setScale(scale, scale);
Bitmap thumbnail = transform(matrix, source, width, height,
OPTIONS_SCALE_UP | options);
return thumbnail;
}
I found an easy way to do this
Bitmap thumbnail = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(mPath),200,200)
Syntax
Bitmap thumbnail = ThumbnailUtils.extractThumbnail(Bitmap source,int width,int height)
OR
use Picasso dependancy
compile 'com.squareup.picasso:picasso:2.5.2'
Picasso.with(context)
.load("file:///android_asset/DvpvklR.png")
.resize(50, 50)
.into(imageView2);
Reference Picasso
If you want high quality result, so use [RapidDecoder][1] library. It is simple as follow:
import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
.scale(width, height)
.useBuiltInDecoder(true)
.decode();
Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result.
This answer is based on the solution presented in https://developer.android.com/topic/performance/graphics/load-bitmap.html (without using of external libraries) with some changes by me to make its functionality better and more practical.
Some notes about this solution:
It is assumed that you want to keep the aspect ratio. In other words:
finalWidth / finalHeight == sourceBitmap.getWidth() / sourceBitmap.getWidth() (Regardless of casting and rounding issues)
It is assumed that you have two values (maxWidth & maxHeight) that you want any of the dimensions of your final bitmap doesn't exceed its corresponding value. In other words:
finalWidth <= maxWidth && finalHeight <= maxHeight
So minRatio has been placed as the basis of calculations (See the implementation). UNLIKE the basic solution that has placed maxRatio as the basis of calculations in actual. Also, the calculation of inSampleSize has been so much better (more logic, brief and efficient).
It is assumed that you want to (at least) one of the final dimensions has exactly the value of its corresponding maxValue (each one was possible, by considering the above assumptions). In other words:
finalWidth == maxWidth || finalHeight == maxHeight
The final additional step in compare to the basic solution (Bitmap.createScaledBitmap(...)) is for this "exactly" constraint. The very important note is you shouldn't take this step at first (like the accepted answer), because of its significant consumption of memory in case of huge images!
It is for decoding a file. You can change it like the basic solution to decode a resource (or everything that BitmapFactory supports).
The implementation:
public static Bitmap decodeSampledBitmap(String pathName, int maxWidth, int maxHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
final float wRatio_inv = (float) options.outWidth / maxWidth,
hRatio_inv = (float) options.outHeight / maxHeight; // Working with inverse ratios is more comfortable
final int finalW, finalH, minRatio_inv /* = max{Ratio_inv} */;
if (wRatio_inv > hRatio_inv) {
minRatio_inv = (int) wRatio_inv;
finalW = maxWidth;
finalH = Math.round(options.outHeight / wRatio_inv);
} else {
minRatio_inv = (int) hRatio_inv;
finalH = maxHeight;
finalW = Math.round(options.outWidth / hRatio_inv);
}
options.inSampleSize = pow2Ceil(minRatio_inv); // pow2Ceil: A utility function that comes later
options.inJustDecodeBounds = false; // Decode bitmap with inSampleSize set
return Bitmap.createScaledBitmap(BitmapFactory.decodeFile(pathName, options),
finalW, finalH, true);
}
/**
* #return the largest power of 2 that is smaller than or equal to number.
* WARNING: return {0b1000000...000} for ZERO input.
*/
public static int pow2Ceil(int number) {
return 1 << -(Integer.numberOfLeadingZeros(number) + 1); // is equivalent to:
// return Integer.rotateRight(1, Integer.numberOfLeadingZeros(number) + 1);
}
Sample Usage, in case of you have an imageView with a determined value for layout_width (match_parent or a explicit value) and a indeterminate value for layout_height (wrap_content) and instead a determined value for maxHeight:
imageView.setImageBitmap(decodeSampledBitmap(filePath,
imageView.getWidth(), imageView.getMaxHeight()));