I searched from a way to create a single imageView from 9 image files like this :
IMG1 - IMG2 - IMG3
IMG4 - IMG5 - IMG6
IMG7 - IMG8 - IMG9
I seen several interesting topics that helps me. One of these talks about a solution that may fits my needs. In this topic, Dimitar Dimitrov propose this :
You can try to do it with the raw data, by extracting the pixel data
from the images as 32-bit int ARGB pixel arrays, merge in one big
array, and create a new Bitmap, using the methods of the Bitmap class
like copyPixelsToBuffer(), createBitmap() and setPixels().
source : Render Two images in ImageView in Android?
So i may extract the 32-bits ARGB pixels from each image file and then create a Bitmap in which i could use the setPixels function to fill in. The problem is that i don't know how i can "extract the 32-bits ARGB pixels from each image file" ...
I also saw things about canvas and surfaceView but i never uses them. Furthermore the final object will only be sometimes pinched zoomed (when the user wants it), so i think it'll be easier for me to make it works using a single imageView ...
So i began with this portion of code inside an AsyncTask (to avoid using the UI Thread)
but i already get an OUT OF MEMORY Exception
...
#Override
protected Bitmap doInBackground(Void... params) {
return this.createBigBitmap();
}
public Bitmap createBigBitmap() {
Bitmap pageBitmap = Bitmap.createBitmap(800, 1066, Bitmap.Config.ARGB_8888); // OUT OF MEMORY EXCEPTION
// create an ArrayList of the 9 page Parts
ArrayList<Bitmap> pageParts = new ArrayList<Bitmap>();
for(int pagePartNum = 1; pagePartNum <= 9; pagePartNum++){
Bitmap pagePartBitmap = getPagePart(pageNum, pagePartNum);
pageParts.add(pagePartBitmap);
}
// try to copy the content of the 9 bitmaps into a single one bitmap
int[] pixels = null;
int offsetX = 0, offsetY = 0, pagePartNum = 0;
for (int x = 0; x < this.nbPageRows; x++) {
for (int y = 0; y < this.nbPageColumns; y++) {
pagePartNum = x * this.nbPageColumns + y;
Bitmap pagePartBitmap = pageParts.get(pagePartNum);
// read pixels from the pagePartBitmap
pixels = new int[pagePartBitmap.getHeight() * pagePartBitmap.getWidth()];
pagePartBitmap.getPixels(pixels, 0, pagePartBitmap.getWidth(), 0, 0, pagePartBitmap.getWidth(), pagePartBitmap.getHeight());
// compute offsetY
if(x == 0)
offsetY = 0;
if(x == 1)
offsetY = pageParts.get(0).getHeight();
if(x == 2)
offsetY = pageParts.get(0).getHeight() * 2;
// compute offsetX
if(y == 0)
offsetX = 0;
if(y == 1)
offsetX = pageParts.get(0).getWidth();
if(y == 2)
offsetX = pageParts.get(0).getWidth() * 2;
// write pixels read to the pageBitmap
pageBitmap.setPixels(pixels, 0, pagePartBitmap.getWidth(), offsetX, offsetY, pagePartBitmap.getWidth(), pagePartBitmap.getHeight());
offsetX += pagePartBitmap.getWidth();
offsetY += pagePartBitmap.getHeight();
}
}
return pageBitmap;
}
// get a bitmap from one of the 9 existing image file page part
private Bitmap getPagePart(int pageNum, int pagePartNum) {
String imgFilename = this.directory.getAbsolutePath()
+ File.separator + "z-"
+ String.format("%04d", pageNum)
+ "-" + pagePartNum + ".jpg";
// ajoute le bitmap de la partie de page
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(imgFilename, opt);
}
Thank you very much
Read BitmapRegionDecoder and this
Edit
Look at Efficient Bitmap Handling in android to come out from OOM error.
Use following code to decode your bitmap.
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = 4;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Related
I am taking 3 pictures in my app before uploading it to a remote server. The output is a byteArray. I am currently converting this byteArray to a bitmap, performing cropping on it(cropping the centre square). I eventually run out of memory(that is after exiting the app coming back,performing the same steps). I am trying to re-use the bitmap object using BitmapFactory.Options as mentioned in the android dev guide
https://www.youtube.com/watch?v=_ioFW3cyRV0&list=LLntRvRsglL14LdaudoRQMHg&index=2
and
https://www.youtube.com/watch?v=rsQet4nBVi8&list=LLntRvRsglL14LdaudoRQMHg&index=3
This is the function I call when I'm saving the image taken by the camera.
public void saveImageToDisk(Context context, byte[] imageByteArray, String photoPath, BitmapFactory.Options options) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
int dimension = getSquareCropDimensionForBitmap(imageWidth, imageHeight);
Log.d(TAG, "Width : " + dimension);
Log.d(TAG, "Height : " + dimension);
//bitmap = cropBitmapToSquare(bitmap);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(imageByteArray, 0,
imageByteArray.length, options);
options.inBitmap = bitmap;
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
options.inSampleSize = 1;
Log.d(TAG, "After square crop Width : " + options.inBitmap.getWidth());
Log.d(TAG, "After square crop Height : " + options.inBitmap.getHeight());
byte[] croppedImageByteArray = convertBitmapToByteArray(bitmap);
options = null;
File photo = new File(photoPath);
if (photo.exists()) {
photo.delete();
}
try {
FileOutputStream e = new FileOutputStream(photo.getPath());
BufferedOutputStream bos = new BufferedOutputStream(e);
bos.write(croppedImageByteArray);
bos.flush();
e.getFD().sync();
bos.close();
} catch (IOException e) {
}
}
public int getSquareCropDimensionForBitmap(int width, int height) {
//If the bitmap is wider than it is tall
//use the height as the square crop dimension
int dimension;
if (width >= height) {
dimension = height;
}
//If the bitmap is taller than it is wide
//use the width as the square crop dimension
else {
dimension = width;
}
return dimension;
}
public Bitmap cropBitmapToSquare(Bitmap source) {
int h = source.getHeight();
int w = source.getWidth();
if (w >= h) {
source = Bitmap.createBitmap(source, w / 2 - h / 2, 0, h, h);
} else {
source = Bitmap.createBitmap(source, 0, h / 2 - w / 2, w, w);
}
Log.d(TAG, "After crop Width : " + source.getWidth());
Log.d(TAG, "After crop Height : " + source.getHeight());
return source;
}
How do I correctly recycle or re-use bitmaps because as of now I am getting OutOfMemory errors?
UPDATE :
After implementing Colin's solution. I am running into an ArrayIndexOutOfBoundsException.
My logs are below
08-26 01:45:01.895 3600-3648/com.test.test E/AndroidRuntime﹕ FATAL EXCEPTION: pool-3-thread-1
Process: com.test.test, PID: 3600
java.lang.ArrayIndexOutOfBoundsException: length=556337; index=556337
at com.test.test.helpers.Utils.test(Utils.java:197)
at com.test.test.fragments.DemoCameraFragment.saveImageToDisk(DemoCameraFragment.java:297)
at com.test.test.fragments.DemoCameraFragment_.access$101(DemoCameraFragment_.java:30)
at com.test.test.fragments.DemoCameraFragment_$5.execute(DemoCameraFragment_.java:159)
at org.androidannotations.api.BackgroundExecutor$Task.run(BackgroundExecutor.java:401)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
P.S : I had thought of cropping byteArrays before, but I did not know how to implement it.
You shouldn't need to do any conversion to bitmaps, actually.
Remember that your bitmap image data is RGBA_8888 formatted. Meaning that every 4 contiguous bytes represents one pixel. As such:
// helpers to make sanity
int halfWidth = imgWidth >> 1;
int halfHeight = imgHeight >> 1;
int halfDim = dimension >> 1;
// get our min and max crop locations
int minX = halfWidth - halfDim;
int minY = halfHeight - halfDim;
int maxX = halfWidth + halfDim;
int maxY = halfHeight + halfDim;
// allocate our thumbnail; It's WxH*(4 bits per pixel)
byte[] outArray = new byte[dimension * dimension * 4]
int outPtr = 0;
for(int y = minY; y< maxY; y++)
{
for(int x = minX; x < maxX; x++)
{
int srcLocation = (y * imgWidth) + (x * 4);
outArray[outPtr + 0] = imageByteArray[srcLocation +0]; // read R
outArray[outPtr + 1] = imageByteArray[srcLocation +1]; // read G
outArray[outPtr + 2] = imageByteArray[srcLocation +2]; // read B
outArray[outPtr + 3] = imageByteArray[srcLocation +3]; // read A
outPtr+=4;
}
}
//outArray now contains the cropped pixels.
The end result is that you can do cropping by hand by just copying out the pixels you're looking for, rather than allocating a new bitmap object, and then converting that back to a byte array.
== EDIT:
Actually; The above algorithm is assuming that your input data is the raw RGBA_8888 pixel data. But it sounds like, instead, your input byte array is the encoded JPG data. As such, your 2nd decodeByteArray is actually decoding your JPG file to the RGBA_8888 format. If this is the case, the proper thing to do for re-sizing is to use the techniques described in "Most memory efficient way to resize bitmaps on android?" since you're working with encoded data.
Try setting more and more variables to null - this helps reclaiming that memory;
after
byte[] croppedImageByteArray = convertBitmapToByteArray(bitmap);
do:
bitmap= null;
after
FileOutputStream e = new FileOutputStream(photo.getPath());
do
photo = null;
and after
try {
FileOutputStream e = new FileOutputStream(photo.getPath());
BufferedOutputStream bos = new BufferedOutputStream(e);
bos.write(croppedImageByteArray);
bos.flush();
e.getFD().sync();
bos.close();
} catch (IOException e) {
}
do:
e = null;
bos = null;
Edit #1
If this fails to help, your only real solution actually using memory monitor. To learn more go here and here
Ps. there is another very dark solution, very dark solution. Only for those who know how to navigate thru dark corners of ofheapmemory. But you will have to follow this path on your own.
I have a resource, "cards.png" which contains all of the cards needed for a deck of cards.
The resource is exactly 896x352, and each card is exactly 64x88.
The "Card" object is a simple one, with a string for the suit, a string for the value, and a bitmap.
I use "Bitmap.createBitmap" to create a subimage from the big original one.
Later, when I draw, it did not appear that the function loaded the images properly. Some cards are "offset" by a number of pixels, and it seems to get worse as I try to read the image from left to right.
I have tried everything, and found that there are a lot of unanswered similar questions here, so wondering if I am going about this the wrong way?
The resource "R.drawable.cards" is referenced in the "/game/res/drawable" folder.
The code for loading the card bitmaps is:
public GameScreen(Context context){
int x=0, y=0;
Bitmap cards = BitmapFactory.decodeResource(context.getResources(), R.drawable.cards);
for (Card card: deck.getCards()){
if (card.getSuit().equals("C")){
y = 0;
} else if (card.getSuit().equals("D")){
y = 88;
} else if (card.getSuit().equals("H")){
y = 176;
} else if (card.getSuit().equals("S")){
y = 264;
} else {
x=0;
y=0;
}
if (card.getValue().equals("A")){
x = 0;
} else if (card.getValue().equals("10")){
x = 576;
} else if (card.getValue().equals("J")){
x = 640;
} else if (card.getValue().equals("Q")){
x = 704;
} else if (card.getValue().equals("K")){
x = 768;
} else if (card.getValue().equals("Joker")){
// Joker
x = 832;
} else {
x = 64 * (Integer.parseInt(card.getValue())-1);
}
Bitmap bitmap = Bitmap.createBitmap(cards, x, y, CardBitmapWidth, CardBitmapHeight);
card.setBitmap(bitmap);
}
}
The code to draw is:
public void draw(Canvas canvas){
int x = 0;
int y = 0;
for (Card card: cards){
canvas.drawBitmap(card.getBitmap(), x, y, null);
x += card.getBitmap().getWidth();
if (x>canvas.getWidth()){
x = 0;
y += card.getBitmap.getHeight();
}
}
}
Make sure the image is not being scaled when loaded:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap cards = BitmapFactory.decodeResource(context.getResources(), R.drawable.cards, options);
My home automation app has a feature where people can upload images to their phone with floorplans and dashboards that they can use to control their home automation software. I have them upload two images: one visible image with the graphics that they want displayed, and a second color map with solid colors corresponding to the objects they want to target areas from the visible image. Both images have to be the same size, pixel-wise. When they tap the screen, I want to be able to get the color from the colormap overlay, and then I proceed to do what ever action has been associated with that color. The problem is, the scaling of the image is screwing me up. The images that they use can be larger than the device screen, so I scale them so they will fit within the display. I don't really need pinch to zoom capability right now, but I might implement it later. For now, I just want the image to be displayed at the largest size so it fits on the screen. So, my question is, how could I modify this code so that I can get the correct touchpoint color from the scaled image. The image scaling itself seems to be working fine. It is scaled and displays correctly. I just can't get the right touchpoint.
final Bitmap bm = decodeSampledBitmapFromFile(visible_image, size, size);
if (bm!=null) {
imageview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageview.setImageBitmap(bm);
}
final Bitmap bm2 = decodeSampledBitmapFromFile(image_overlay, size, size);
if (bm2!=null) {
overlayimageview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
overlayimageview.setImageBitmap(bm2);
imageview.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent mev) {
DecodeActionDownEvent(v, mev, bm2);
return false;
}
});
}
private void DecodeActionDownEvent(View v, MotionEvent ev, Bitmap bm2)
{
xCoord = Integer.valueOf((int)ev.getRawX());
yCoord = Integer.valueOf((int)ev.getRawY());
try {
// Here's where the trouble is.
// returns the value of the unscaled pixel at the scaled touch point?
colorTouched = bm2.getPixel(xCoord, yCoord);
} catch (IllegalArgumentException e) {
colorTouched = Color.WHITE; // nothing happens when touching white
}
}
private static Bitmap decodeSampledBitmapFromFile(String fileName,
int reqWidth, int reqHeight) {
// code from
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(fileName, options);
}
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// code from
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
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;
}
I figured it out. I replaced
xCoord = Integer.valueOf((int)ev.getRawX());
yCoord = Integer.valueOf((int)ev.getRawY());
with
Matrix inverse = new Matrix();
v.getImageMatrix().invert(inverse);
float[] touchPoint = new float[] {ev.getX(), ev.getY()};
inverse.mapPoints(touchPoint);
xCoord = Integer.valueOf((int)touchPoint[0]);
yCoord = Integer.valueOf((int)touchPoint[1]);
After 7 years I have to provide another answer, because after upgrading to LG G8s from LG G6 the code from accepted answer stopped returning the correct color.
This solution works on all the phones I found at home, but who knows... maybe even this one is not universal.
(Using Xamarin C# but the principle is easy to understand)
First we have to get the actual ImageView size as float to get floating point division
private float imageSizeX;
private float imageSizeY;
private void OnCreate(...) {
...
FindViewById<ImageView>(Resource.Id.colpick_Image).LayoutChange += OnImageLayout;
FindViewById<ImageView>(Resource.Id.colpick_Image).Touch += Image_Touch;
...
}
//Event called every time the layout of our ImageView is updated
private void OnImageLayout(object sender, View.LayoutChangeEventArgs e) {
imageSizeX = Image.Width;
imageSizeY = Image.Height;
Image.LayoutChange -= OnImageLayout; //Just need it once then unsubscribe
}
//Called every time user touches/is touching the ImageView
private void Image_Touch(object sender, View.TouchEventArgs e) {
MotionEvent m = e.Event;
ImageView img = sender as ImageView;
int x = Convert.ToInt32(m.GetX(0));
int y = Convert.ToInt32(m.GetY(0));
Bitmap bmp = (img.Drawable as BitmapDrawable).Bitmap;
// The scale value (how many times is the image bigger)
// I only use it with images where Width == Height, so I get
// scaleX == scaleY
float scaleX = bmp.Width / imageSizeX;
float scaleY = bmp.Height / imageSizeY;
x = (int)(x * scaleX);
y = (int)(y * scaleY);
// Check for invalid values/outside of image bounds etc...
// Get the correct Color ;)
int color = bmp.GetPixel(x, y);
}
static Boolean[][] squares = new Boolean[32][32];
static BitmapFactory.Options opts = new BitmapFactory.Options();
public static Boolean[][] getFrame(int id){
opts.inPreferredConfig = Bitmap.Config.ALPHA_8;
opts.outHeight = 32;
opts.outWidth = 32;
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), R.raw.f11, opts);
for (int y = 0; y < 32; y++) {
for (int x = 0; x < 32; x++) {
int pixel = bmp.getPixel(x, y);
if(pixel == -1)
squares[x][y] = false;
else
squares[x][y] = true;
}
}
return squares;
}
I'm having an issue here, bitmap factory seems to not be importing my bitmaps correctly. Here's what the original looks like and here's what getPixel (and getPixels) returns me. This happens with and without any options declared. I'd like to know why it seems to be importing 2rds of the picture at 2x resolution. I have tried 1 bit and 4 bit bitmaps as well as declaring the 1 bit and 4 bit Bitmap.Config. Using the Boolean array data to draw rectangles in a grid on a canvas. Thanks in advance.
I suppose you noticed it on screen?
If so, what is the xml for the ImageView? Maybe you inadvertently set an erroneous width or height?
Might be a late answer but I would try
opts.inScaled = false;
I have an Android application to display an image on another image, such that second image's white colour is transparent. To do this, I have used two ImageViews, with the original image to be overlaid as bitmap1 and the image to be made transparent as bitmap2. When I run this, I get some exceptions at the setPixel method.
Here's my code:
Bitmap bitmap2 = null;
int width = imViewOverLay.getWidth();
int height = imViewOverLay.getHeight();
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
if(bitMap1.getPixel(x, y) == Color.WHITE)
{
bitmap2.setPixel(x, y, Color.TRANSPARENT);
}
else
{
bitmap2.setPixel(x, y, bitMap1.getPixel(x, y));
}
}
}
imViewOverLay is the ImageView of the overlay image. Any idea what might be going wrong in the above code?
The most obvious error is that you're not creating bitmap2 - unless you've not posted all the code of course.
You declare it and set it to null, but then don't do anything else until you try to call bitmap2.setPixel.
i think you need to make it mutable
Loading a resource to a mutable bitmap
i did this
BitmapFactory.Options bitopt=new BitmapFactory.Options();
bitopt.inMutable=true;
mSnareBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.snare, bitopt);
also, i found i needed to set alpha to something less than 255 for it to render the image with transparent background.
mPaint.setAlpha(250);
canvas.drawBitmap(mSnareBitmap, 0, 30, mPaint);
by the way, using white as your transparent color isn't a great idea because you will get aliasing problems at the edges of your opaque objects. i use green because my overlay images don't have any green in (like a green screen in the movies) then i can remove the green inside the loop and set the alpha value based on the inverse of the green value.
private void loadBitmapAndSetAlpha(int evtype, int id) {
BitmapFactory.Options bitopt=new BitmapFactory.Options();
bitopt.inMutable=true;
mOverlayBitmap[evtype] = BitmapFactory.decodeResource(getResources(), id, bitopt);
Bitmap bm = mOverlayBitmap[evtype];
int width = bm.getWidth();
int height = bm.getHeight();
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
int argb = bm.getPixel(x, y);
int green = (argb&0x0000ff00)>>8;
if(green>0)
{
int a = green;
a = (~green)&0xff;
argb &= 0x000000ff; // save only blue
argb |= a; // put alpha back in
bm.setPixel(x, y, argb);
}
}
}
}