Android - how to get or set (print) DPI ( dots per inch ) of JPEG file while loading or saving it programmatically?
I have an app developed on Android versions 4.0 and above. ( The app does not support Android versions below 4.0 [Ice Cream Sandwich] ).
The question is related to (print) DPI of various images ( for eg. of jpeg or png ) format.
This question does NOT relate to SCREEN DPI or sizes of various Android devices. It is also NOT related to showing the Bitmap on the device in screen size.
I am using the following code to load the image file in 'Bitmap'. Then I have been cropping it and saving it to another file in JPEG format with jpegCompression. I have been able to do this by the following code, but I am unable to get DPI of loaded or set the DPI of saved Image file.
So I have two questions.
1) How can I get the (print) DPI from the JPEG file, after or while loading it in 'Bitmap'?
2) While saving the new generated 'Bitmap', how can I set the DPI again in the JPEG file?
Following is the part of code for reference.
FileInputStream inputStream = new FileInputStream(theSourcePhotoFilePathName);
Bitmap bitmap = null;
BitmapRegionDecoder decoder = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inDensity = 300; // Tried this but not working.
try {
decoder = BitmapRegionDecoder.newInstance(in, false);
bitmap = decoder.decodeRegion(region, options); // the region has cropping coordinates.
} catch (IllegalArgumentException e){
Log.d("First Activity", "Failed to recycle bitmap for rect=" + region, e);
} catch (IOException e) {
Log.d("First Activity", "Failed to decode into rect=" + region, e);
} finally {
if (decoder != null) decoder.recycle();
}
inputStream.close();
inputStream = null;
FileOutputStream fos = new FileOutputStream( theTargetTempFolderDestFilePath );
bitmap.compress(CompressFormat.JPEG, jpegCompressionRatio, fos);
fos.flush();
fos.close();
fos = null;
I have tried to find from stackoverflow and from other sites by googling, but could not get the proper related answer. So I have decided to ask it in this forum.
Your hints and suggestions are welcome.
Sanjay.
Solution 1:
Just for convenience here is ready to copy and paste method:
public static void saveBitmapToJpg(Bitmap bitmap, File file, int dpi) throws IOException {
ByteArrayOutputStream imageByteArray = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageByteArray);
byte[] imageData = imageByteArray.toByteArray();
setDpi(imageData, dpi);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(imageData);
fileOutputStream.close();
}
private static void setDpi(byte[] imageData, int dpi) {
imageData[13] = 1;
imageData[14] = (byte) (dpi >> 8);
imageData[15] = (byte) (dpi & 0xff);
imageData[16] = (byte) (dpi >> 8);
imageData[17] = (byte) (dpi & 0xff);
}
saved file will have properly set Image DPI value:
Solution 2:
Are you referring to the DPI metaData written as part of the JPEG file? I have struggled with that recently but came up with a solution. If you have already solved it, this answer can help others who run into the same problem.
When a Bitmap is compressed to JPEG in Android, it saves it in a JFIF segment format. Please see article here(http://en.wikipedia.org/wiki/JPEG_File_Interchange_Format). I have also attached a screenshot of an Android jpeg image opened up in a hex editor so you can see how it matches.
To edit the value, you need to first create a byte[] array that will store the Bitmap.compress(). Here is a part of my code where I do just that(input being the source Bitmap).
ByteArrayOutputStream uploadImageByteArray = new ByteArrayOutputStream();
input.compress(Bitmap.CompressFormat.JPEG, 100, uploadImageByteArray);
byte[] uploadImageData = uploadImageByteArray.toByteArray();
Based on the JFIF structure, you need to edit the 13th, 14th, 15th, 16th, and 17th indexes in the byte array. 13th specifying the density type, 14th and 15th the X resolution, and 16th and 17th holding the Y resolution. In my case, I changed it to 500 dpi as the X and Y resolution in the metadata. This translated to 0000 0001 1111 0100 which is 1F4 in hex. I then wrote the byte[] to file, copied it from my phone to my computer, and verified the details were present when viewing the image properties.
uploadImageData[13] = 00000001;
uploadImageData[14] = 00000001;
uploadImageData[15] = (byte) 244
uploadImageData[16] = 00000001;
uploadImageData[17] = (byte) 244
File image = new File(Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath() + "/meta Images/", imageFileName);
FileOutputStream sdCardOutput = new FileOutputStream(image);
sdCardOutput.write(uploadImageData);
NOTE: Java uses a signed byte system and you cannot enter in any binary value above 127 without the compiler barking at you. I ran into this problem inputting F4 as a byte. The solution is to convert your value to decimal and then use a (byte) cast.
Hope this helps!
Solution 3:
The given answers are about how to set dpi. In order to get dpi of an image:
final ImageInfo imageInfo = Sanselan.getImageInfo(image_file);
final int physicalWidthDpi = imageInfo.getPhysicalWidthDpi();
final int physicalHeightDpi = imageInfo.getPhysicalHeightDpi();
The gradle dependency: implementation group: 'org.apache.sanselan', name: 'sanselan', version: '0.97-incubator'