I am using android custom camera to captured JPG images, But not able to preview them on windows photo viewer. Can anyone please advise. Images are visible using other applications like Ms Paint, Office, Windows 10 Photo application.
As of Android 10, Bitmap.compress() seems to have added an ICC profile.
This issue was caused by an incorrect version value in the added ICC profile.
This issue was reported and fixed on the skia issue tracker.
I don't know when it will be fixed on Android.
https://bugs.chromium.org/p/skia/issues/detail?id=12491
So, deleting the ICC profile from the jpeg image generated with Bitmap.compress() seems to be the best way at the moment. To solve it programmatically, you need to parse the JPEG data generated with Bitmap.compress() to delete the ICC profile segment.
Update 2022-09-07: This issue is fixed in Android 13. ( SKIA SkICC.cpp )
This is an example code to delete ICC Profile segment. Please refer to Exiv2's "The Metadata in JPEG files" for how the ICC Profile is embedded in the jpeg file.
Update 2022-10-28: Fixed incorrect APP2 segment copy
public class JpegFixer {
private static final int MARKER = 0xff;
private static final int MARKER_SOI = 0xd8;
private static final int MARKER_SOS = 0xda;
private static final int MARKER_APP2 = 0xe2;
private static final int MARKER_EOI = 0xd9;
public void removeIccProfile(InputStream inputStream, String outputPath) throws IOException {
FileOutputStream outputStream = new FileOutputStream( outputPath );
if ( inputStream.read() != MARKER || inputStream.read() != MARKER_SOI ) {
throw new IOException( "Not a JPEG image" );
}
outputStream.write( MARKER );
outputStream.write( MARKER_SOI );
while ( true ) {
int marker0 = inputStream.read();
int marker1 = inputStream.read();
if ( marker0 != MARKER ) {
throw new IOException( "Invalid marker:" + Integer.toHexString( marker0 & 0xff ) );
}
int length0 = inputStream.read();
int length1 = inputStream.read();
final int segmentLength = (length0 << 8 & 0xFF00) | (length1 & 0xFF);
final int length = segmentLength - 2;
if ( length < 0 ) {
throw new IOException( "Invalid length" );
}
if ( marker1 == MARKER_EOI || marker1 == MARKER_SOS ) {
outputStream.write( marker0 );
outputStream.write( marker1 );
outputStream.write( length0 );
outputStream.write( length1 );
copy( inputStream, outputStream );
break;
}
// ICC_PROFILE\0"
if ( marker1 == MARKER_APP2 && length >= 12 + 2 ) {
byte[] buffer = new byte[12];
read( inputStream, buffer );
if ( buffer[0] == 'I' && buffer[1] == 'C' &&
buffer[2] == 'C' && buffer[3] == '_' &&
buffer[4] == 'P' && buffer[5] == 'R' &&
buffer[6] == 'O' && buffer[7] == 'F' &&
buffer[8] == 'I' && buffer[9] == 'L' &&
buffer[10] == 'E' && buffer[11] == 0 ) {
// skip segment
inputStream.skip( length - buffer.length );
}
else {
// copy segment
outputStream.write( marker0 );
outputStream.write( marker1 );
outputStream.write( length0 );
outputStream.write( length1 );
outputStream.write( buffer );
copy( inputStream, outputStream, length - buffer.length );
}
}
else {
// copy segment
outputStream.write( marker0 );
outputStream.write( marker1 );
outputStream.write( length0 );
outputStream.write( length1 );
copy( inputStream, outputStream, length );
}
}
}
public void read(InputStream is, byte[] buffer) throws IOException {
int totalBytesRead = 0;
while ( totalBytesRead != buffer.length ) {
final int bytesRead = is.read( buffer, totalBytesRead, buffer.length - totalBytesRead );
if ( bytesRead == -1 ) {
throw new EOFException( "End of data reached." );
}
totalBytesRead += bytesRead;
}
}
private int copy(InputStream is, OutputStream out, int numBytes) throws IOException {
int remainder = numBytes;
byte[] buffer = new byte[8192];
while ( remainder > 0 ) {
int n = is.read( buffer, 0, Math.min( remainder, buffer.length ) );
if ( n == -1 ) {
break;
}
remainder -= n;
out.write( buffer, 0, n );
}
return numBytes;
}
private int copy(InputStream is, OutputStream os) throws IOException {
int total = 0;
byte[] buffer = new byte[8192];
int c;
while ( (c = is.read(buffer)) != -1 ) {
total += c;
os.write( buffer, 0, c );
}
return total;
}
}
This is obviously caused by an ICC color profile that will be added to the bitmaps by Android. The Windows photo viewer isn't capable to show the images with that ICC color profile.
I did not find out yet how this ICC profile is being generated, I assume that this is done by the Android Bitmap.compress function.
Removing the profile with e.g. ImageMagick will "repair" the files and they will open in photo viewer too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With