Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows photo viewer does not open bitmap compressed images from Android studio

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.

enter image description here

like image 449
Vishal Pawar Avatar asked Sep 11 '25 17:09

Vishal Pawar


2 Answers

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;
    }

}

like image 197
Blue Ocean Avatar answered Sep 14 '25 08:09

Blue Ocean


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.

like image 27
hschottm Avatar answered Sep 14 '25 08:09

hschottm