Android: Uploading image without losing Exif data - android

In our app users have been uploading millions of images for years using (roughly) this code:
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(postFilePath, bmOptions);
Bitmap roughBitmap = BitmapFactory.decodeFile(postFilePath, bmOptions);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
roughBitmap.compress(Bitmap.CompressFormat.JPEG, 70, stream);
InputStream fis = new ByteArrayInputStream(stream.toByteArray());
int fileSize = stream.toByteArray().length;
conn.setRequestProperty("Content-Length", Integer.toString(fileSize));
conn.setFixedLengthStreamingMode(fileSize);
...
if (fis != null) {
byte[] buf = new byte[10240];
int read;
while ((read = fis.read(buf)) > 0) {
os.write(buf, 0, read);
totalBytesRead += read;
if (uploadProgressListener != null) {
try {
uploadProgressListener.onBytesUploaded(read);
} catch (Exception e) {
Log.e(e);
}
}
}
fis.close();
}
Recently we saw the need to preserve the Exif data of uploaded images. The problem is that the image Exif data is lost when compressing the bitmap. I thought of using ExifInterface for extracting this data from the original file:
ExifInterface oldExif = new ExifInterface(postFilePath);
String value = oldExif.getAttribute(ExifInterface.TAG_DATETIME);
..and then adding it to the InputStream fis and then continue uploading the file. The problem is that ExifInterface cannot save Exif data to an InputStream.
How can Exif data be retained in the images when they'er uploaded to the server?
It's not a duplicate:
Just to clarify deeper, I've tried using the suggested duplicate question by using this method:
public static void copyExif(String originalPath, InputStream newStream) throws IOException {
String[] attributes = new String[]
{
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_DATETIME_DIGITIZED,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_SUBSEC_TIME,
ExifInterface.TAG_WHITE_BALANCE
};
ExifInterface oldExif = new ExifInterface(originalPath);
ExifInterface newExif = new ExifInterface(newStream);
if (attributes.length > 0) {
for (int i = 0; i < attributes.length; i++) {
String value = oldExif.getAttribute(attributes[i]);
if (value != null)
newExif.setAttribute(attributes[i], value);
}
newExif.saveAttributes();
}
}
.. but got the exception java.io.IOException: ExifInterface does not support saving attributes for the current input. after newExif.saveAttributes(); because I'm trying to save the attributes to an InputStream. How else can I do it?

My solution:
As #amuttsch and #CommonsWare suggested, I:
saved the scaled/compressed bitmap to a temp file
copied the exif from the original file to the temp file
converted the temp file to a byte array and sent it to upload
.. then I found out that the server strips the Exif again while generating image variants :-P but that's another story which server guys are now working to correct.
Main code:
...
// Copy original Exif to scaledBitmap
String tempFilePath = getTempFilePath(postFilePath);
try {
FileOutputStream out = new FileOutputStream(tempFilePath);
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 70, out);
copyExif(postFilePath, tempFilePath);
} catch (Exception e) {
e.printStackTrace();
}
// Get stream from temp (exif loaded) file
File tempFile = new File(tempFilePath);
byte[] byteFile = readFile(tempFile);
fis = new ByteArrayInputStream(byteFile);
// Remove the temp file
boolean deleted = tempFile.delete();
// Finalize
int fileSize = byteFile.length;
conn.setRequestProperty("Content-Length", Integer.toString(fileSize));
conn.setFixedLengthStreamingMode(fileSize);
...
getTempFilePath():
private String getTempFilePath(String filename) {
String temp = "_temp";
int dot = filename.lastIndexOf(".");
String ext = filename.substring(dot + 1);
if (dot == -1 || !ext.matches("\\w+")) {
filename += temp;
} else {
filename = filename.substring(0, dot) + temp + "." + ext;
}
return filename;
}
copyExif():
public static void copyExif(String originalPath, String newPath) throws IOException {
String[] attributes = new String[]
{
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_DATETIME_DIGITIZED,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_SUBSEC_TIME,
ExifInterface.TAG_WHITE_BALANCE
};
ExifInterface oldExif = new ExifInterface(originalPath);
ExifInterface newExif = new ExifInterface(newPath);
if (attributes.length > 0) {
for (int i = 0; i < attributes.length; i++) {
String value = oldExif.getAttribute(attributes[i]);
if (value != null)
newExif.setAttribute(attributes[i], value);
}
newExif.saveAttributes();
}
}
readFile():
public static byte[] readFile(File file) throws IOException {
// Open file
RandomAccessFile f = new RandomAccessFile(file, "r");
try {
// Get and check length
long longlength = f.length();
int length = (int) longlength;
if (length != longlength)
throw new IOException("File size >= 2 GB");
// Read file and return data
byte[] data = new byte[length];
f.readFully(data);
return data;
} finally {
f.close();
}
}

The problem is that the image Exif data is lost when compressing the bitmap
The EXIF data is lost when reading in the Bitmap. A Bitmap has no EXIF tags.
How can Exif data be retained in the images when they'er uploaded to the server?
Stop reading in the Bitmap. Just upload the contents of postFilePath as-is. It will contain whatever EXIF tags it contains.
My assumption is that you are reading in the Bitmap in the hope that saving it again in 70% JPEG quality will result in meaningful bandwidth savings. I suspect that you are not saving very much, and you may be increasing the bandwidth in some cases (e.g., postFilePath points to a PNG). Your costs are a chunk of CPU time, an increased risk of an OutOfMemoryError, and the loss of your EXIF tags.
If, instead, the convert-to-70%-JPEG is some sort of data normalization approach, do that work on the server, where you have more CPU power, more disk space, more RAM, and continuous power.

Source: https://stackoverflow.com/a/11572752/8252521
Answered by: https://stackoverflow.com/users/1592398/code-jaff
Convert the file to bitmap by
Bitmap bi = BitmapFactory.decode(filepath + "DSC00021.jpg");
You can specify options too, look at API documentation
Or if you want to exchange the meta data from one file to another,
sanselan will probably be the best choice. This would be much
helpful when you manipulating the image, for example re-size.
The sample code will guide you in a right direction.

You need to just create a new OutputStream to preserve the Exif Information. There is no need of creating a new File.

Related

Image size increased more after convert to Base64

I am using Compressor third party library for compress the captured images size its working fine and now size is showing KB's but when i convert this images to BASE64 file size becomes 6MB or more size showing my code is below can some one help me please what should i do for resolve this issue
code:
File file= new Compressor(this).compressToFile(f);
String base64File = getBase64StringFile(file);
// Converting File to Base64.encode String type using Method
public static String getBase64StringFile(File f) {
InputStream inputStream = null;
String encodedFile= "", lastVal;
try {
inputStream = new FileInputStream(f.getAbsolutePath());
byte[] buffer = new byte[10240];//specify the size to allow
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
Base64OutputStream output64 = new Base64OutputStream(output, Base64.DEFAULT);
while ((bytesRead = inputStream.read(buffer)) != -1) {
output64.write(buffer, 0, bytesRead);
}
output64.close();
encodedFile = output.toString();
}
catch (FileNotFoundException e1 ) {
e1.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
lastVal = encodedFile;
return lastVal;
}
You can resolve this issue using some other Compressor tools like FFMPEG.
Base64 always increase your file size
Base64 is often used on binary data that needs to be transmitted across a system that isn't really designed for binary. Depending on what you're doing, you may not even need to encode it. And per the wikipedia, on average, a file is expected to grow about 37% when you base64 encode it, which is almost exactly what your numbers are.

Android - Load image byte by byte in imageview

I have a large size of the image file which is nearly 16MB size. I want to load this image in my imageView and zoom it after that add markers. I tried this with subsampling-scale-image-view. I am following the below link https://github.com/davemorrissey/subsampling-scale-image-view .
The important point is I am loading image from url. The above library not supporting that. So I just downloaded the image and save to SD card after that load from that local file. Technically which is working.
Issue:
Now the issue is it is taking too much of time for the first time downloading. Also even second time it takes nearly a minute.
My Idea:
Due to this issue, I try to load image byte by byte. Once the image is downloading 100bytes then show that in imageView next download the next part of the image from url. Is it possible to doing like that?
Currently I am loading image like the following code:
URL url = new URL(url_);
URLConnection conection = url.openConnection();
conection.connect();
// getting file length
int lenghtOfFile = conection.getContentLength();
// input stream to read file - with 8k buffer
InputStream input = new BufferedInputStream(url.openStream(), 8192);
// Output stream to write file
OutputStream output = new FileOutputStream(root+"/"+ fileName);
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
// writing data to file
output.write(data, 0, count);
}
// flushing output
output.flush();
// closing streams
output.close();
input.close();
runOnUiThread(new Runnable() {
#Override
public void run() {
image.setImage(ImageSource.uri(root+"/"+ fileName));
}
});
Can somebody help me to solve this riddle?
Note: If there are any possibilities other than this library pls add your suggestions.
Never tried this but you can check if this work.
Get the data from url in form of byte Array.
data = getImageStream(url); //should call in async Task..
Now convert byte array into bitmap and set in imageView.
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
image.setImageBitmap(bitmap)
Not writing into file.This will help with some performance improvement.
public byte[] getImageStream(String url){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = null;
try {
is = url.openStream ();
byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time.
int n;
while ( (n = is.read(byteChunk)) > 0 ) {
baos.write(byteChunk, 0, n);
}
}
catch (IOException e) {
System.err.printf ("Failed while reading bytes from %s: %s", url.toExternalForm(), e.getMessage());
e.printStackTrace ();
// Perform any other exception handling that's appropriate.
}
finally {
if (is != null) { is.close(); }
}
return baos.toByteArray();
}

Android: Base64 Sample issue

currently I'm using kSoap to publish data to C# web services. Now I've come to the part where I need to upload images from my machine using Base64Binary. I searched everywehre internet but couldn't come up with a proper solution.
there is a solution with external Base64 class example but I'm interested in native solution as there is a API from Android 2.2.
Since I'm a newbie I couldn't do it myself. Basically I have a sd card file path of images, I want to convert them into Base64 format to upload.
Hope someone can help me or direct me to proper documents.
Thanks in advance.
Please try this, use Base64.java from link you have specified.
Bitmap bmImage = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + File.separator + "Your filename");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmImage.compress(Bitmap.CompressFormat.JPEG, 100, baos);
String encodedString = Base64.encodeBytes( baos.toByteArray() );
You should change extension of this Bitmap.CompressFormat.JPEG statement according to your file type. You can decode a base 64 string to image using following code
byte[] b;
b = Base64.decode("base 64 string");
final Bitmap unscaledBitmap = BitmapFactory.decodeByteArray(b, 0, b.length );
I figured out the issue
String Image64 = null;
try {
Image64 = Base64.encodeToString((getBytesFromFile(new File(path))), DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
//encording
public static byte[] getBytesFromFile(File file) throws IOException {
InputStream is = new FileInputStream(file);
// Get the size of the file
long length = file.length();
// You cannot create an array using a long type.
// It needs to be an int type.
// Before converting to an int type, check
// to ensure that file is not larger than Integer.MAX_VALUE.
if (length > Integer.MAX_VALUE) {
// File is too large
}
// Create the byte array to hold the data
byte[] bytes = new byte[(int)length];
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length
&& (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < bytes.length) {
throw new IOException("Could not completely read file "+file.getName());
}
// Close the input stream and return bytes
is.close();
return bytes;
}

Image uploaded from the android app space seems corrupted

In my android application, I need to upload a image in my Assets/Drawable/raw folder to the server.
I tried the following:
InputStream fileInputStream;
if(imageChanged) {
File file = New File("filename");
fileInputStream = new FileInputStream(file);
}else {
fileInputStream = ctx.getAssets().open("default.png");
}
int bytesAvailable;
byte[] buffer = new byte[102400];
while((bytesAvailable = fileInputStream.available()) > 0) {
int bufferSize = Math.min(bytesAvailable, 102400);
if(bufferSize<102400){
buffer = new byte[bufferSize];
}
int bytesRead = fileInputStream.read(buffer, 0,bufferSize);
dos.write(buffer, 0, bytesRead);
}
This executes fine. I am able to read the inputstream and write bytes to the DataOutputStream, the image is uploaded to the server.
Anyhow, the image at the server appears to be corrupted - only for the default image (uploaded in the 'else' block. The 'if' block image is not getting corrupted)
I also tried placing default.png in the 'raw' folder and tried the below
fileInputStream = ctx.getResources().openRawResource(R.drawable.default);
Same result here - the image at the server is corrupted.
I am starting to doubt if this is because the default.png is in the application space.
Can I get some help towards the proper way to upload an image in the application space (drawable/asset/raw)?
thanks!
nimi
It might have to do with the buffer size? I tried two different methods to read/write a png from the assets folder and both produced a working image. I used FileOutputStream to write to the sdcard but that should not be an issue.
InputStream is, is2;
FileOutputStream out = null, out2 = null;
try {
//method 1: compressing a Bitmap
is = v.getContext().getAssets().open("yes.png");
Bitmap bmp = BitmapFactory.decodeStream(is);
String filename = Environment.getExternalStorageDirectory().toString()+File.separator+"yes.png";
Log.d("BITMAP", filename);
out = new FileOutputStream(filename);
bmp.compress(Bitmap.CompressFormat.PNG, 90, out);
//method 2: Plain stream IO
String filename2 = Environment.getExternalStorageDirectory().toString()+File.separator+"yes2.png";
out2 = new FileOutputStream(filename2);
Log.d("BITMAP", filename2);
int r, i=0;
is2 = v.getContext().getAssets().open("yes.png");
while ((r = is2.read()) != -1) {
Log.d ("OUT - byte " + i, "Value: " + r);
out2.write(r);
i++;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
if (out2 != null)
out2.close();
} catch (IOException e) {
e.printStackTrace();
}
}

Android : Getting this error while downloading image from remote server: SkImageDecoder::Factory returned null

I am trying to download images from a remote server, the number of images downloaded is 30. The code i am using to download image is as below. Some images download successfully and some images don't download and raises the above exception. What might be the problem.
public static Bitmap loadBitmap(String url)
{
Bitmap bitmap = null;
InputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new URL(url).openStream(), 4*1024);
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, 4 * 1024);
int byte_;
while ((byte_ = in.read()) != -1)
out.write(byte_);
out.flush();
final byte[] data = dataStream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 1;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length,options);
} catch (IOException e) {
Log.e("","Could not load Bitmap from: " + url);
} finally {
try{
in.close();
out.close();
}catch( IOException e )
{
System.out.println(e);
}
}
return bitmap;
}
Please look on my this post
Image download code works for all image format, issues with PNG format rendering
In my case I solved this error with encode the url
Because image url that i wanted to download has the Persian letters(or other Unicode character) in it
So I replaced all Persian characters with encoded UTF-8 letters

Categories

Resources