2

I am 100% sure the problem here is with the actual image. However I hope that the solution is some attribute of the image that will help others in the future.

The image:

the photo in question http://soundwave.robotsidekick.com/mlsphotos.jpg

I have tried loading this image in several ways. I have downloaded it and tried loading it in an ImageView:

    final ImageView v = (ImageView) findViewById(R.id.image);
    v.setImageResource(R.drawable.photo);
    v.invalidate();

I have tried loading it from a url:

    final String[] params = new String[] {
            "",
    };

    (new AsyncTask<String, Bitmap, Bitmap>()
            {
        @Override
        protected Bitmap doInBackground(final String... params)
        {
            Bitmap ret = null;
            for (final String url : params)
            {
                try
                {
                    Log.e(TAG, url);
                    ret = BitmapFactory.decodeStream((new URL(url)).openStream());
                    publishProgress(ret);
                }
                catch (final MalformedURLException e)
                {
                    Log.e(TAG, "Malformed URL", e);
                }
                catch (final IOException e)
                {
                    Log.e(TAG, "IO Exception", e);
                }
            }
            return ret;
        }

        @Override
        protected void onProgressUpdate(final Bitmap... values)
        {
            super.onProgressUpdate(values);

            for (final Bitmap result : values)
            {
                if (result != null)
                {
                    final ImageView v = (ImageView) MainActivity.this.findViewById(R.id.image);
                    v.setImageBitmap(result);
                    v.invalidate();
                }
            }
        }
    }).execute(params);

I have also tried loading the image in a WebView like this:

    final WebView webview = (WebView) findViewById(R.id.webview);
    webview.loadData("<html><body><img src=\"" + url + "\"></body></html>", "text/html", "utf-8");
    webview.invalidate();

I have also tried loading the image in Browser (the app) and that does not work.

None of those work, HOWEVER if I load the url into Chrome on Android it works great (not in Browser), if I load the image on my desktop browser (Chrome, Firefox, etc) it loads great. I have checked that the mime type matches the extension and I am just at a loss.

EDIT

There is a work around for images coming from an InputStream where the bitmap processing runs out of data on the stream before the stream completes. The work around is documented in this question and this bug.

However this is a corrupt image whose data ends prematurely. I know that means I am already down a broken track, but I am hoping to have some better error handling than Android passing me back null and I lose. iOS, Chrome (on device and computer) as well as most other places seem to have much better error handling. Is there something I can do on Android to handle corrupt jpgs?

EDIT 2

There has to be a solution here because Chrome on my device handles this situation elegantly. However the closest I can come to fixing this is the following code:

final InputStream is = (new URL(url)).openStream();
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final int size = 1024;
int len = -1;
byte[] buf = new byte[size];
while ((len = is.read(buf, 0, size)) != -1)
{
    bos.write(buf, 0, len);
}
buf = bos.toByteArray();

// buf is now filled with the corrupted bytes of the image

ret = BitmapFactory.decodeByteArray(buf, 0, buf.length);

// ret is null because it was a corrupt jpg

With that code I can check if there are bytes and the image wasn't decoded. Then at least I can tell I have a corrupt image (or not an image) and can report something slightly more useful to the user (like hey I have an image here with 16K but I sure don't know what to do with it).

Anyone know how Chrome manages to decode as much of the image as they can before they hit the corruption?

Community
  • 1
  • 1
xbakesx
  • 13,202
  • 6
  • 48
  • 76
  • your image does look broken, though – njzk2 Oct 01 '12 at 15:50
  • Yeah, I just changed it to an image that doesn't look broken. – xbakesx Oct 01 '12 at 15:51
  • what happens when it doesn't load ? – njzk2 Oct 01 '12 at 15:53
  • When I load the image from a url the `ImageView` never gets updated, because decodeBitmap returns null. When I load the image from a resource, the `ImageView` looks like I never tried loading a resource. The image is a jpg the mime type comes back as image/jpeg from `curl` – xbakesx Oct 01 '12 at 15:56
  • i would recommend png, but i guess you don't have a choice ? – njzk2 Oct 01 '12 at 16:01
  • Well we can get into a debate about file types all we want, but your assumption is correct that I do not have a choice. I have to take what they are giving me. – xbakesx Oct 01 '12 at 16:05
  • (also, that's not about debating, png has a much better support on android, and that google who says so, not me). I guess BitmapFactory returns null without any exception ? – njzk2 Oct 01 '12 at 16:07
  • Correct, so there is no exception BitmapFactory just returns null. There are two messages logged in the console: "fAsset->read(1024) returned 0" and "decoder->decode returned false" which led me to this question/answer: http://stackoverflow.com/questions/2787015/skia-decoder-fails-to-decode-remote-stream this seems to be my problem, but the work arounds aren't working for me (and this bug: http://code.google.com/p/android/issues/detail?id=6066) – xbakesx Oct 01 '12 at 16:17

3 Answers3

2

I opened the file in Photoshop CS6 and it said that the file may be damaged, possibly truncated or incomplete. The file can be opened. If I save it in Photoshop without making any changes, it then works in Android. I'm afraid I don't know exactly what's wrong with the image though.

Jimbali
  • 2,065
  • 1
  • 20
  • 24
  • Thanks for the feedback, it definitely seems like the image only has half the data in it. – xbakesx Oct 01 '12 at 16:19
  • It loads in Chrome for Android for me too but not in the Samsung browser. Chrome uses different jpeg decoding I think. You can get some of the source code here: https://developers.google.com/chrome/mobile/docs/faq but it's mostly in C++ so you'd have to use the NDK if you wanted to try and use it. – Jimbali Oct 03 '12 at 20:53
2

Here is the important bit about JPGs from Wikipedia and here's a question that ultimate led me to the solution.

I just appended the two closing jpeg end of image bytes to the stream, in order to convince the decoder that the stream is done with image data. This method is flawed because JPGs can have JPGs inside them, meaning appending one set of end of image bytes, doesn't guarantee that we closed all the images.

In both solutions below I assume is is an input stream for a JPG image. Also these two constants are defined:

private static final int JPEG_EOI_1 = 0xFF;
private static final int JPEG_EOI_2 = 0xD9;

This method we read all the bytes into memory then try to decode the bytes:

final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final int size = 1024;
int len = -1;
final byte[] buf = new byte[size];
try
{
    while ((len = is.read(buf, 0, size)) != -1)
    {
        bos.write(buf, 0, len);
    }
    bos.write(JPEG_EOI_1);
    bos.write(JPEG_EOI_2);

    final byte[] bytes = bos.toByteArray();

    return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
catch (final IOException ex)
{
    return null;
}
catch (final Exception ex)
{
    return null;
}

This method creates a stream wrapper that makes sure the last two bytes are JPG end of image bytes:

return BitmapFactory.decodeStream(new JpegClosedInputStream(is));

// And here's the stream wrapper
class JpegClosedInputStream extends InputStream
{
    private final InputStream inputStream;
    private int bytesPastEnd;

    private JpegClosedInputStream(final InputStream iInputStream)
    {
        inputStream = iInputStream;
        bytesPastEnd = 0;
    }

    @Override
    public int read() throws IOException
    {
        int buffer = inputStream.read();
        if (buffer == -1)
        {
            if (bytesPastEnd > 0)
            {
                buffer = JPEG_EOI_2;
            }
            else
            {
                ++bytesPastEnd;
                buffer = JPEG_EOI_1;
            }
        }

        return buffer;
    }
}
Community
  • 1
  • 1
xbakesx
  • 13,202
  • 6
  • 48
  • 76
-2

do setcontentview and show it from the xml like that:

//photo.xml
  <ImageView
        android:id="@+id/picture"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_gravity="center"
        android:src="@drawable/photo" />
/>
//Photo.java
setContentView(R.layout.photo);
mydDeveler
  • 71
  • 2
  • 10
  • EDIT: Nevermind, I see, and that didn't change anything. /* I'm not sure I understand. Is that the entire contents of photo.xml? Or do I have to wrap it in a `LinearLayout` or something else? */ – xbakesx Oct 01 '12 at 16:28
  • yes sure you have to make LinearLayout and inside of it you paste this code – mydDeveler Oct 01 '12 at 18:24