321
  Bitmap bmp   = intent.getExtras().get("data");
  int size     = bmp.getRowBytes() * bmp.getHeight();
  ByteBuffer b = ByteBuffer.allocate(size);

  bmp.copyPixelsToBuffer(b);

  byte[] bytes = new byte[size];

  try {
     b.get(bytes, 0, bytes.length);
  } catch (BufferUnderflowException e) {
     // always happens
  }
  // do something with byte[]

When I look at the buffer after the call to copyPixelsToBuffer the bytes are all 0... The bitmap returned from the camera is immutable... but that shouldn't matter since it's doing a copy.

What could be wrong with this code?

Tom Fobear
  • 6,729
  • 7
  • 42
  • 74

10 Answers10

707

Try something like this:

Bitmap bmp = intent.getExtras().get("data");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
bmp.recycle();
jknair0
  • 1,194
  • 13
  • 24
Mezm
  • 7,212
  • 1
  • 14
  • 9
  • 11
    Won't this cause problems if the image is not of type PNG? – pgsandstrom Sep 19 '12 at 12:38
  • 10
    won't because the Bitmap is a decoded image regardless what is was, like a pixel array. It will compress as a PNG, which will not lose quality at compression –  Jan 08 '13 at 10:01
  • 5
    better is the rewind option from @Ted Hopp — compressing it is a waste of CPU unless your goal is an encoded image.... – Kaolin Fire Apr 08 '13 at 22:34
  • 44
    In my experience, on low-memory system such as Android, must be attention to add bitmap.recycle(); just after the compression, and close the stream to avoid the memory leak exception. – Son Huy TRAN Dec 10 '13 at 16:38
  • 1
    I presume this only works with ARGB_8888 images due to PNG respecting alpha channels, tried this with an RGB_565 bitmap; the original byte length was roughly 3 times as many bytes as what byteArrays length was after compression suggesting that the compression went wrong somewhere due to it expecting argb8888 I presume? – alex.p Apr 30 '14 at 20:51
  • 12
    This approach is really wastefull of allocations. Your ```ByteArrayOutputStream``` will allocate a ```byte[]``` of size equal to the ```byte[]``` backing your ```Bitmap```, then ```ByteArrayOutputStream.toByteArray()``` will again allocate yet another ```byte[]``` of the same size. – zyamys May 20 '14 at 18:51
  • 1
    hmm. I am just getting a black image! – Geo Paul Sep 28 '14 at 08:57
  • 2
    this method destroys exif data of the given image. – stdout Jul 13 '15 at 14:25
  • @Bugs Happen Yes, using JNI – zyamys Dec 07 '15 at 03:41
  • @syamys you should probably post your solution and let's see if it's better – Sheychan Jan 05 '16 at 06:57
  • Note that for PNGs the quality setting is redundant. "Some formats, like PNG which is lossless, will ignore the quality setting" – Peter Chaula Dec 12 '16 at 09:06
  • @Mezm I have a question [here](https://stackoverflow.com/questions/46997683/sending-byte-array-of-approx-fixed-size-everytime) in which I am using Byte Buffer and I am confusing on how can I limit the size of byte array before sending to some other method. Wanted to see if you can help out. – john Oct 30 '17 at 00:20
  • The result is quite poor, i have some lines in the image and when scaled down they are not smooth. – Cristi Băluță Apr 29 '19 at 06:59
88

CompressFormat is too slow...

Try ByteBuffer.

※※※Bitmap to Byte-array※※※

int size = bitmap.getRowBytes() * bitmap.getHeight();
ByteBuffer byteBuffer = ByteBuffer.allocate(size);
bitmap.copyPixelsToBuffer(byteBuffer);
byte[] byteArray = byteBuffer.array();

// Somehow save (and later load) these as well:
int width = bitmap.getWidth();
int height = bitmap.getHeight();
String format = bitmap.getConfig().name();

※※※Byte-array to Bitmap※※※

Bitmap.Config configBmp = Bitmap.Config.valueOf(format);
Bitmap bitmap_tmp = Bitmap.createBitmap(width, height, configBmp);
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
bitmap_tmp.copyPixelsFromBuffer(buffer);
Top-Master
  • 7,611
  • 5
  • 39
  • 71
朱西西
  • 1,005
  • 8
  • 7
  • 8
    Since this question has the Android tag, converting bytes back to a Bitmap can also be done with a one-liner: `Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length)` where `bytes` is your byte array – automaton Aug 29 '18 at 22:22
  • Perhaps big/small endian should be considered? – NeoWang Jun 15 '19 at 07:38
  • If you want to save the byte array in local DB(Sqlite, Room), you should compress like upper answer! – J.Dragon Feb 17 '20 at 16:22
  • 1
    Note, however, that without the compression the size difference is dramatic. For theory you could read wikipedia, but for example in my case the compressed result (as per the 1st answer) is 20 MB, the other one (this answer) is 48 MB – Kirill Starostin May 21 '20 at 15:33
27

Here is bitmap extension .convertToByteArray wrote in Kotlin.

/**
 * Convert bitmap to byte array using ByteBuffer.
 */
fun Bitmap.convertToByteArray(): ByteArray {
    //minimum number of bytes that can be used to store this bitmap's pixels
    val size = this.byteCount

    //allocate new instances which will hold bitmap
    val buffer = ByteBuffer.allocate(size)
    val bytes = ByteArray(size)

    //copy the bitmap's pixels into the specified buffer
    this.copyPixelsToBuffer(buffer)

    //rewinds buffer (buffer position is set to zero and the mark is discarded)
    buffer.rewind()

    //transfer bytes from buffer into the given destination array
    buffer.get(bytes)

    //return bitmap's pixels
    return bytes
}
Tomas Ivan
  • 2,212
  • 2
  • 21
  • 34
20

Do you need to rewind the buffer, perhaps?

Also, this might happen if the stride (in bytes) of the bitmap is greater than the row length in pixels * bytes/pixel. Make the length of bytes b.remaining() instead of size.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • 6
    `rewind()` is the key. I was getting the same `BufferUnderflowException` and rewinding the buffer after filling it resolved this. – tstuts Feb 27 '13 at 16:00
13

Use below functions to encode bitmap into byte[] and vice versa

public static String encodeTobase64(Bitmap image) {
    Bitmap immagex = image;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    immagex.compress(Bitmap.CompressFormat.PNG, 90, baos);
    byte[] b = baos.toByteArray();
    String imageEncoded = Base64.encodeToString(b, Base64.DEFAULT);
    return imageEncoded;
}

public static Bitmap decodeBase64(String input) {
    byte[] decodedByte = Base64.decode(input, 0);
    return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length);
}
alexrnov
  • 2,346
  • 3
  • 18
  • 34
Amol Suryawanshi
  • 2,108
  • 21
  • 29
6

Your byte array is too small. Each pixel takes up 4 bytes, not just 1, so multiply your size * 4 so that the array is big enough.

MindJuice
  • 4,121
  • 3
  • 29
  • 41
  • 4
    His byte array is large enough. `getRowBytes()` takes the 4 bytes per pixel in to account. – tstuts Feb 27 '13 at 15:58
4

Ted Hopp is correct, from the API Documentation :

public void copyPixelsToBuffer (Buffer dst)

"... After this method returns, the current position of the buffer is updated: the position is incremented by the number of elements written in the buffer. "

and

public ByteBuffer get (byte[] dst, int dstOffset, int byteCount)

"Reads bytes from the current position into the specified byte array, starting at the specified offset, and increases the position by the number of bytes read."

CaptainCrunch
  • 1,230
  • 14
  • 15
3

In order to avoid OutOfMemory error for bigger files, I would recommend to solve the task by splitting a bitmap into several parts and merging their parts' bytes.

private byte[] getBitmapBytes(Bitmap bitmap)
{
    int chunkNumbers = 10;
    int bitmapSize = bitmap.getRowBytes() * bitmap.getHeight();
    byte[] imageBytes = new byte[bitmapSize];
    int rows, cols;
    int chunkHeight, chunkWidth;
    rows = cols = (int) Math.sqrt(chunkNumbers);
    chunkHeight = bitmap.getHeight() / rows;
    chunkWidth = bitmap.getWidth() / cols;

    int yCoord = 0;
    int bitmapsSizes = 0;

    for (int x = 0; x < rows; x++)
    {
        int xCoord = 0;
        for (int y = 0; y < cols; y++)
        {
            Bitmap bitmapChunk = Bitmap.createBitmap(bitmap, xCoord, yCoord, chunkWidth, chunkHeight);
            byte[] bitmapArray = getBytesFromBitmapChunk(bitmapChunk);
            System.arraycopy(bitmapArray, 0, imageBytes, bitmapsSizes, bitmapArray.length);
            bitmapsSizes = bitmapsSizes + bitmapArray.length;
            xCoord += chunkWidth;

            bitmapChunk.recycle();
            bitmapChunk = null;
        }
        yCoord += chunkHeight;
    }
    
    return imageBytes;
}


private byte[] getBytesFromBitmapChunk(Bitmap bitmap)
{
    int bitmapSize = bitmap.getRowBytes() * bitmap.getHeight();
    ByteBuffer byteBuffer = ByteBuffer.allocate(bitmapSize);
    bitmap.copyPixelsToBuffer(byteBuffer);
    byteBuffer.rewind();
    return byteBuffer.array();
}
Ayaz Alifov
  • 8,334
  • 4
  • 61
  • 56
2

I think this will do -

public static byte[] convertBitmapToByteArray(Bitmap bitmap){
        ByteBuffer byteBuffer = ByteBuffer.allocate(bitmap.getByteCount());
        bitmap.copyPixelsToBuffer(byteBuffer);
        byteBuffer.rewind();
        return byteBuffer.array();
    }
Aarush Kumar
  • 149
  • 1
  • 8
0

Try this to convert String-Bitmap or Bitmap-String

/**
 * @param bitmap
 * @return converting bitmap and return a string
 */
public static String BitMapToString(Bitmap bitmap){
    ByteArrayOutputStream baos=new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG,100, baos);
    byte [] b=baos.toByteArray();
    String temp=Base64.encodeToString(b, Base64.DEFAULT);
    return temp;
}

/**
 * @param encodedString
 * @return bitmap (from given string)
 */
public static Bitmap StringToBitMap(String encodedString){
    try{
        byte [] encodeByte=Base64.decode(encodedString,Base64.DEFAULT);
        Bitmap bitmap= BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
        return bitmap;
    }catch(Exception e){
        e.getMessage();
        return null;
    }
}
Mohammad nabil
  • 1,010
  • 2
  • 12
  • 23