EDIT: This is a bug in Android version <4.3 Kitkat. It relates to the libjpeg library in Android, which can't handle JPEGs with missing EOF/EOI bits, or apparently with metadata/EXIF data that it doesn't like.
https://code.google.com/p/android/issues/detail?id=9064
ORIGINAL QUESTION:
I have an issue when loading an image in my app.
My endpoint sends JSON which contains a BASE64 encoded image. Depending on the REST call, these images can be PNG or JPG. Some of the JPG files suffer from an issue where they are missing an EOF bit at the end. The PNG files work, and some JPG files work, but unfortunately a lot of these JPG files with the issue are present in the Oracle DB (stored as BLOB). I don't have control of the DB.
I have been looking through Google bugs here:
https://code.google.com/p/android/issues/detail?id=9064
and here:
https://code.google.com/p/android/issues/detail?id=57502
The issue is also seen where the encoding is CYMK using a custom ICC profile.
Decoding the image the standard way returns false:
byte[] imageAsBytes = Base64.decode(base64ImageString, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.length);
According to the bug reports above, the built in JPG parser in Android is to blame.
I'm trying to figure out a workaround for my device, which is stuck on 4.2.2. I have no other option on this OS version.
I thought it might be a good idea to try and use an image loader library like Universal Image Loader, but it requires I either have the image stored locally, or stored on a URL. As I get the data in BASE64 from the REST server, I can't use this. An option is to support decodeByteArray in a custom class that extends BaseImageDecoder, as stated by the dev at the bottom here: https://github.com/nostra13/Android-Universal-Image-Loader/issues/209
Here's where I get stuck. I already have a custom image decoder to try handle the issue of the missing EOF marker in the JPG file, but I don't know how to edit it to add support for decodeByteArray.
Here is my CustomImageDecoder:
public class CustomImageDecoder extends BaseImageDecoder {
public CustomImageDecoder(boolean loggingEnabled) {
super(loggingEnabled);
}
#Override
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
InputStream stream = decodingInfo.getDownloader()
.getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
return stream == null ? null : new JpegClosedInputStream(stream);
}
private class JpegClosedInputStream extends InputStream {
private static final int JPEG_EOI_1 = 0xFF;
private static final int JPEG_EOI_2 = 0xD9;
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;
}
}
}
By the way, using the above custom class, I am trying to load my byte array like this:
byte[] bytes = Base64.decode(formattedB64String, Base64.NO_WRAP);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
String imageId = "stream://" + is.hashCode();
...
ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(imageId, userImage, options);
and I get this error:
ImageLoader: Image can't be decoded [stream://1097215584_656x383]
Universal Image loader does not allow the stream:// schema, so I created a custom BaseImageDownloader class that allows it:
public class StreamImageDownloader extends BaseImageDownloader {
private static final String SCHEME_STREAM = "stream";
private static final String STREAM_URI_PREFIX = SCHEME_STREAM + "://";
public StreamImageDownloader(Context context) {
super(context);
}
#Override
protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException {
if (imageUri.startsWith(STREAM_URI_PREFIX)) {
return (InputStream) extra;
} else {
return super.getStreamFromOtherSource(imageUri, extra);
}
}
}
So if anyone can help me create a better CustomImageDecoder that handles a BASE64 encoded string, or a byte[] containing an image so I can use decodeByteArray, I would be grateful!
Thank you.
UnversalImageLoader uses the following schemes to decode the files
"h t t p ://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
your scheme is stream://
Hope that helps.
Just to close this off:
The issue here is actually a bug in Android <4.3 where Android can't display images that either aren't closed properly (missing end bytes) or contain certain metadata that, for some reason, it doesn't like. I'm not sure what metadata this is, however. My issue was with JPEGs not being terminated properly.
The bug is fixed in Android 4.3 anyway.
Related
I have a Xamarin application which plays remote(internet) audio files using the MediaPlayer with the following setup:
_mediaPlayer.SetDataSource(mediaUri);
_mediaPlayer.PrepareAsync();
Now I would like to change the implementation to also cache files. For the caching part I found a really nice library called MonkeyCache which saves the files in this JSON format:
{"$type":"System.Byte[], mscorlib","$value":"UklGRhAoAgBXQVZFZm10IBAAAAABAAIARKwAABCxAgAEABAAZGF0YdAnAgAAAAAAAAAAAAAAAAAA+P/4/+z/7P8KAAoA7//v//L/8v8JAAkA6f/p//j/+P8FAAUA6P/o/wEAAQD+//7/5//n ................."}
So my MediaPlayer setup has now changed to:
if (Barrel.Current.Exists(mediaUri)){
var audio = Barrel.Current.Get<byte[]>(mediaUri);
_mediaPlayer.SetDataSource(???);
}else{
using (var webClient = new WebClient()){
var downloadDataBytes = webClient.DownloadData(mediaUri);
if (downloadDataBytes != null && downloadDataBytes.Length > 0)
{
Barrel.Current.Add(mediaUri, downloadDataBytes, TimeSpan.FromDays(1));
_mediaPlayer.SetDataSource(???);
}
}
}
I would like to play the audio from a byte[] instead of the mediaUri.
Is there any way to actually play an in memory byte[]?
The only solutions that I could find were to create a FileInputStream out of a File by using a filepath, but the implementation of the MonkeyCache actually hashes the file name, before adding it:
static string Hash(string input){
var md5Hasher = MD5.Create();
var data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
return BitConverter.ToString(data);
}
Therefore the downloaded bytes, will be saved under:
/data/data/com.package.name.example/cache/com.package.name.example/MonkeyCacheFS/81-D6-E8-62-F3-4D-F1-64-A6-A1-53-46-34-1E-FE-D1
Even if I were to use the same hashing logic to actually compute the file path myself and use the FileInputStream which might be working by what I've read, it would defeat the purpose of using the var audio = Barrel.Current.Get<byte[]>(mediaUri); functionality of the MonkeyCache.
However, if that is the only way, I will do it.
Edit: Even with my described approach, it would probably not work right away as even if I compute the right file name, it is still in JSON format.
Edit2: A working solution is:
var audio = Barrel.Current.Get<byte[]>(mediaUri);
var url = "data:audio/mp3;base64," + Convert.ToBase64String(audio);
_mediaPlayer.SetDataSource(url);
Finally figured it out:
The MediaPlayer.SetDataSource method has an overload which expects a MediaDataSource object.
MediaDataSource
For supplying media data to the framework. Implement
this if your app has special requirements for the way media data is
obtained
Therefore, you can use your implementation of the MediaDataSource and feed it your byte array.
public class StreamMediaDataSource : MediaDataSource
{
private byte[] _data;
public StreamMediaDataSource(byte[] data)
{
_data = data;
}
public override long Size
=> _data.Length;
public override void Close()
{
_data = null;
}
public override int ReadAt(long position, byte[] buffer, int offset, int size)
{
if (position >= _data.Length)
{
return -1;
}
if (position + size > _data.Length)
{
size -= (Convert.ToInt32(position) + size) - _data.Length;
}
Array.Copy(_data, position, buffer, offset, size);
return size;
}
}
which will lead to the MediaPlayer data source to be
_mediaPlayer.SetDataSource(new StreamMediaDataSource(downloadedDataBytes));
Edit: This only works for API >= 23. I still have to find a solution for API<23(which I still haven't).
Edit2: For API < 23 support I used the
var url = $"data:audio;base64,{Convert.ToBase64String(downloadedDataBytes)}";
_mediaPlayer.SetDataSource(url);
approach.
I am building an android application where a user can view some listed video. Those videos are categories into some channel. Once a channel is selected by user I want to cache all the video related to that channel in my cache memory so can play the video when there is no internet also.
Can anyone have more understanding about video cache without playing please help me in understanding how I can achieve this task.
Right now I am able to cache video If it's played using some library.
I have find the following working solution for caching video in background (single/multiple) using below lib, no need of player/video_view.use AsyncTaskRunner
Videocaching Lib
Add following in line in your gradle file
compile 'com.danikula:videocache:2.7.0'
Since we just need to kick start the prefetching, no need to do anything in while loop.
Or we can use ByteArrayOutputStream to write down the data to disk.
URL url = null;
try {
url = new URL(cachingUrl(cachingUrl));
InputStream inputStream = url.openStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
//nothing to do
}
} catch (IOException e) {
e.printStackTrace();
}
Important code from lib. to do
Create static instance in application class using following code
private HttpProxyCacheServer proxy;
public static HttpProxyCacheServer getProxy(Context context) {
Applications app = (Applications) context.getApplicationContext();
return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
}
private HttpProxyCacheServer newProxy() {
//return new HttpProxyCacheServer(this);
return new HttpProxyCacheServer.Builder(this)
.cacheDirectory(CacheUtils.getVideoCacheDir(this))
.maxCacheFilesCount(40)
.maxCacheSize(1024 * 1024 * 1024)
.build();
}
Write following code in your activity to pass url
public String cachingUrl(String urlPath) {
return Applications.getProxy(this).getProxyUrl(urlPath, true);
}
I am getting jp2 in base64 format from server end. I am able to convert jpg to jp2 form ImageMagick library and send to server. They are able to convert it to jpg using Buffered Image and ImageIo .
But I am not getting any idea to convert jp2 to jpg and render in Imageview.
Hoping for any help. Thanks in advance.
You probably solved it somehow already, but in case you're still looking for a solution, you can try the JP2 for Android library. (Disclaimer: I wrote the library.) It's based on OpenJPEG, like the DimaArts' response, but it has a bit nicer Java interface.
Add the following dependency to your build.gradle file:
implementation 'com.gemalto.jp2:jp2-android:1.0'
and use the following code to decode the JP2:
byte[] jp2Data = ...; //get the JP2 data from somewhere
Bitmap bmp = new JP2Decoder(jp2Data).decode();
imgView.setImageBitmap(bmp);
You can use OpenJpeg library for decode Jpeg2000. You can use compiled library https://github.com/DimaArts/OpenJpegAndroid. It contains an example of encode jpeg2000. Library supports PNG input and output formats for decoder and encoder.
Try this:
OpenJPEGJavaDecoder decoder = new OpenJPEGJavaDecoder();
String[] params2 = new String[4];
params2[0] = "-i";
params2[1] = mInputPath; // path to jp2
params2[2] = "-o";
params2[3] = mOutputPath // path to png
decoder.decodeJ2KtoImage(params2);
if you are using JNI:
public int decodeJ2KtoImage(String[] parameters) {
return internalDecodeJ2KtoImage(parameters);
}
Try this code from https://stackoverflow.com/a/39103107/2760681
private static void convertImage(int randNum) throws IOException {
try {
File foundFile = new File("c:\\images\\" + randNum + ".jp2");
BufferedImage background = ImageIO.read(foundFile);
ImageIO.write(background, "jpg", new File("c:\\images\\" + randNum + ".jpg"));
System.out.println("jpg file is generated");
} catch (Exception e) {
// TODO: handle exception
System.out.println("No file " + randNum +".jp2 found");
}
}
All my images are encrypted in Android file system. When I need to show them, I need to decrypt, generate the bitmap and then delete the file. I'm trying to use Picasso to load my images. I created an RequestHandler to decrypt and load image.
RequestHandler accepts two types of result:
1. the bitmap or 2. a stream.
I'm trying to return the stream. That way Picasso can load images using the best practices, prevent out of memory. I created an custom Stream class and override the Dispose() method to delete the decrypted file after use.
The problem is: The stream is not disposing, neither closing, after the image is loaded, and I can't for automatic dispose by GAC (I'm using Xamarin/C#). Any ideas? What can I do?
UPDATE (19/01/17): I found out a small bug in my code and after fixing it, my problem was solved. But here is my custom RequestHandler for future reference... EncryptedFileStream is my custom stream that wraps the original stream and delete the decrypted file on Dispose().
public class EncryptedFilenameRequestHandler : RequestHandler
{
private readonly Context _context;
private readonly ICriptoService _criptoService;
public EncryptedFilenameRequestHandler(Context context, ICriptoService criptoService)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (criptoService == null) throw new ArgumentNullException(nameof(criptoService));
_context = context;
_criptoService = criptoService;
}
public override bool CanHandleRequest(Request request)
{
var uri = request.Uri;
return string.Compare(uri.Scheme, Constantes.AppSchema, true) == 0 &&
string.Compare(uri.Authority, Constantes.Host, true) == 0 &&
string.Compare(uri.Path, "/loadimagem/filename/encrypted", true) == 0;
}
public override Result Load(Request request, int networkPolicy)
{
string password = request.Uri.GetQueryParameter("p");
string encryptedFilename = request.Uri.GetQueryParameter("f");
string decryptedFilename = System.IO.Path.Combine(AppEnviroment.GetTempDirectory(), Guid.NewGuid().ToString("N"));
if (string.IsNullOrWhiteSpace(encryptedFilename) || !File.Exists(encryptedFilename))
return null;
_criptoService.Decrypt(encryptedFilename, decryptedFilename, password);
//retorna um stream para leitura do arquivo descriptografado
var uri = Android.Net.Uri.FromFile(new Java.IO.File(decryptedFilename));
var stream = new EncryptedFileStream(decryptedFilename, _context.ContentResolver.OpenInputStream(uri));
return new Result(stream, Picasso.LoadedFrom.Disk);
}
}
I found out a small bug in my code and, after fixing it, my problem was solved. The code for EncryptedFilenameRequestHandler posted in the question is working without any problem.
Abstract:
reading images from file
with toggled bits to make unusable for preview tools
cant use encryption, to much power needed
can I either optimize the code below, or is there a better approach
Longer description:
I am trying to improve my code, maybe you got some ideas or improvements for the following situation. Please be aware that I neither try to beat the CIA, nor care much if somebody "brakes" the encryption.
The background is simple: My app loads a bunch of images from a server into a folder on the SD card. I do NOT want the images to be simple JPG files, because in this case the media indexer would list them in the library, and a user could simply copy the whole folder to his harddrive.
The obvious way to go is encryption. But a full blown AES or other encryption does not make sense, for two reasons: I would have to store the passkey in the app, so anyone could get the key with some effort anyway. And the price for decrypting images on the fly is way too high (we are talking about e.g. a gallery with 30 200kB pictures).
So I decided to toggle some bits in the image. This makes the format unreadable for image tools (or previews), but is pretty easy undone when reading the images. For "encrypting" I use some C# tool, the "decrypt" lines are the following ones:
public class CustomInputStream extends InputStream {
private String _fileName;
private BufferedInputStream _stream;
public CustomInputStream(String fileName) {
_fileName = fileName;
}
public void Open() throws IOException {
int len = (int) new File(_fileName).length();
_stream = new BufferedInputStream(new FileInputStream(_fileName), len);
}
#Override
public int read() throws IOException {
int value = _stream.read() ^ (1 << 7);
return value;
}
#Override
public void close() throws IOException {
_stream.close();
}
}
I tried overwriting the other methods (read with more then one byte) too, but this kills the BitmapFactory - not sure why, maybe I did something wrong. Here is the code for the image bitmap creation:
Bitmap bitmap = null;
try {
InputStream i = CryptoProvider.GetInstance().GetDecoderStream(path);
bitmap = BitmapFactory.decodeStream(i);
i.close();
} catch (Exception e1) {
_logger.Error("Cant load image " + path + " ERROR " + e1);
}
if (bitmap == null) {
_logger.Error("Image is NULL for path " + path);
}
return bitmap;
Do you have any feedback on the chosen approach? Any way to optimize it, or a completely different approach for Android devices?
You could try XORing the bytestream with the output of a fast PRNG. Just use a different seed for each file and you're done.
note: As already noted in the question, such methods are trivial to bypass.