MauiImage resource accessible as stream in Android - android

I'm migrating an app from Xamarin.Forms to MAUI. Everything is going surprisingly smoothly so far!
There are a small number of images added as SVG resources to the app that are displaying at fixed locations in the app (ie defined in the XAML). The app generates views in a SkiaSharp Canvas from large amounts of downloaded content and that content includes images downloaded from the cloud which are rendering fine (as SKBitmap from the file stream).
A few of the image references in the downloaded content are for the SVG resources that are being included in the app. I have determined a way to get the stream for the resource images in iOS but I cannot find any way to do this in Android.
In iOS:
var imageStream = await FileSystem.OpenAppPackageFileAsync(resourceName + ".png");
This throws a 'FileNotFoundException' when running on Android...
What I've found DOES work in Android:
#if ANDROID
// The only reference I can find that actually loads the image in any way
var drawable = MainApplication.Current.GetDrawable(resourceId);
height = drawable?.IntrinsicHeight ?? 0;
width = drawable?.IntrinsicWidth ?? 0;
#endif
However the Drawable returned (native Android type) has no way that I can find of converting into a Stream and I can't find any way in SkiaSharp libraries to just dump the Drawable onto the SKCanvas?
Diving into the packages generated I can see that the iOS IPA package has the SVGs saved as PNGs in the root of the package (likely why I can load them using OpenAppPackageFileAsync) while in the Android APK the images are split out into /res/drawable-XXXXXXX folders (where XXXXXXX is hdpi / ldrti / xhdpi / etc for the different device resolutions).

Related

Android - can vector drawables store GIF files/animated images?

I have made a custom keyboard that allows users to send GIFs/Images.
Currently, whenever the user taps on a ImageView, the following code runs. It checks whether the drawable inside the ImageView is animated (i.e - the image is a GifDrawable) and if not, it casts it to a BitmapDrawable. context.contentComitter.commit() is responsible for turning the casted drawables into files that can be read by other apps.
if(holder.imageView.drawable is GifDrawable) {
context.contentCommitter.commitGifDrawable(holder.imageView.drawable, fileName)
} else {
context.contentCommitter.commitBitmapDrawable(holder.imageView.drawable as BitmapDrawable, fileName)
}
The problem is - I'm getting the following crash for certain images on Samsung Galaxy S9s only:
Fatal Exception: java.lang.ClassCastException
android.graphics.drawable.VectorDrawable cannot be cast to android.graphics.drawable.BitmapDrawable
The solution seems to be simple - just perform another check for whether holder.imageView.drawable is a VectorDrawable, and if so, extract the Bitmap and write it to a file.
However, the problem is that the VectorDrawable source seems to be a GIF file, instead of a static image. This is confusing because the methods in the VectorDrawable documentation indicate that it is only used to store static images.
Is it possible for a VectorDrawable to contain animated images (i.e - GIFs), or do VectorDrawables only contain static images?
Here is the image database: https://github.com/vedantroy/Image-Database

Display Bitmaps in Xamarin Forms created in Android and iOS

There are the Bitmap for Android and UIImage for iOS. Is there a way to display both somehow in the Xamarin Forms Image control?
Obviously I need the Dependency Service. I will have two implementations that create either a bitmap or an uiimage using some source, but how do I bring those two products together to a single forms control? Both Android and iOS methods have to return something, that the image control can understand and display. I don't know what that might be.
Edit: I look for a way where I don't use storage space, if possible.
Edit2:
I tried Jasons suggestion and it works fine.
I create a bitmap in the Android project and return a MemoryStream object:
MemoryStream stream = new MemoryStream();
newImage.Compress(Bitmap.CompressFormat.Png, 0, stream);
return stream;
Then I consume it in my Xamarin.Forms Image control:
var stream = DependencyService.Get<ICrossPlatformImageProcesor>().Combine_Images(imagePath);
stream.Position = 0;
img_ImageView.Source = Xamarin.Forms.ImageSource.FromStream(() => stream);
I will have two implementations that create either a bitmap or an uiimage using some source, but how do I bring those two products together to a single forms control?
You can simply use Image Control of xamarin forms, images can be loaded specifically for each platform, or they can be downloaded for display.
For more information, you can refer to Working with Images.
I look for a way where I don't use storage space, if possible.
I'm not quite understand this, if you mean don't use memory, then I think it is not possible. If you mean your images are not saved in storage, then possibly you have an URL address on internet of your images?
Anyway, Image control in Xamarin.Forms support image source form ImageSource instance, file, Uri, and resources, to load image from uri, you can simply code like this:
var webImage = new Image { Aspect = Aspect.AspectFit };
webImage.Source = ImageSource.FromUri(new Uri("https://xamarin.com/content/images/pages/forms/example-app.png"));

Firemonkey: Loading a JPEG into any type of image control then saving to file or stream makes it smaller. Can this be avoided?

In a mobile app one or more images (only jpeg/jpg) - either taken with built-in camera or selected from photo library - are displayed before uploading to a web service.
But loading an image into a FMX TImage / TImageControl / TImageViewer and saving it to stream (or file) makes it smaller, about 45%.
Any idea why this happens and is there a way to avoid the reduction in size ?
As asked for this is the simple test code, nothing special to it:
procedure TImageTest.btnTestClick(Sender: TObject);
var
aFile : string;
begin
if not OpenDialog.Execute then
Exit;
// get jpg file name for loading
aFile := OpenDialog.Filename;
// load into TImage, TImageControl or TImageViewer
ImageViewer.Bitmap.LoadFromFile(aFile);
// and save to file for comparison
ImageViewer.Bitmap.SaveToFile(aFile+'_2.jpg');
end;
The ideal solution to avoid losing image quality is by not relying on the visual controls to store your data. Instead, keep a background object containing the raw image file, and only use the visual controls to display this image to the user. When it comes to saving the image, save it from this background object rather than the visual control.
The same concept applies for about all aspects of programming - visual controls are only meant for visual display to the user, not for storing data.

Android PdfDocument file size

I want to generate a PDF File from a View using the PdfDocument android class introduced in KitKat. I managed to do it, and the file is so far generated ok, ending up having a correct PDF. The only problem is the file is huge, 12Mb for just one page. Is there a way to reduce the File size?
The code I am using to generate the PDF is:
public static File generateDocument(Activity activity, String fileName, ViewGroup container) throws IOException{
File f = new File(activity.getExternalFilesDir(null), fileName);
PdfDocument document = new PdfDocument();
try{
for(int i=0;i<container.getChildCount();i++){
View v = container.getChildAt(i);
PdfDocument.PageInfo.Builder pageBuilder = new PdfDocument.PageInfo.Builder(v.getWidth(), v.getHeight(), i);
Page page = document.startPage(pageBuilder.create());
v.draw(page.getCanvas());
document.finishPage(page);
}
document.writeTo(new FileOutputStream(f));
} finally{
if(document!=null){
document.close();
}
}
return f;
}
In case anyone is still looking for a solution... I was working on a project to generate PDF from images and not satisfied with the file size generated by both Android's PdfDocument and 3rd party AndroidPdfWriter APW.
After some trials I ended up using Apache's PdfBox, which gave me a PDF file (A4 size with a single 1960x1080 image) for around 80K, while it's usually 2~3M with PdfDocument or AndroidPdfWriter.
PDDocument document = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
// Define a content stream for adding to the PDF
contentStream = new PDPageContentStream(document, page);
Bitmap bimap = _get_your_bitmap_();
// Here you have great control of the compression rate and DPI on your image.
// Update 2017/11/22: The DPI param actually is useless as of current version v1.8.9.1 if you take a look into the source code. Compression rate is enough to achieve a much smaller file size.
PDImageXObject ximage = JPEGFactory.createFromImage(document, bitmap, 0.75, 72);
// You may want to call PDPage.getCropBox() in order to place your image
// somewhere inside this page rect with (x, y) and (width, height).
contentStream.drawImage(ximage, 0, 0);
// Make sure that the content stream is closed:
contentStream.close();
document.save(_your_file_path_);
document.close();
=====
btw. I guess the reason why they generate a huge file size is because they don't compress the image data while writing to PDF file. If you take a look into AndroidPdfWriter's XObjectImage.deflateImageData() method you will see it's using java.util.zip.Deflater.NO_COMPRESSION option to write the image data which is kind of horrible if you've got a picture with size 1960x1080. If you change the options to e.g. Deflater.BEST_COMPRESSION you get much smaller file size however it takes up to 3-4 seconds for me to handle one single page which is not acceptable.
There are a few main things that increases the size of a PDF file:
hi-resolution pictures (where lo-res would suffice)
embedded fonts (where content would still be readable "good enough" without them)
PDF content not required any more for the current version/view (older version of certain objects)
embedded ICC profiles
embedded third-party files (using the PDF as a container)
embedded job tickets (for printing)
embedded Javascript
and a few more
Try using iText. Following links give a basice idea for iText in android.
http://technotransit.wordpress.com/2011/06/17/using-itext-in-android/
http://www.mysamplecode.com/2013/05/android-itext-pdf-bluetooth-printer.html
https://stackoverflow.com/a/21025162/3110609
Using PDFDocument, be sure to downscale your images prior to drawing them in the canvas.
When drawing to the screen, this is enough to scale the bitmap :
canvas.drawBitmap(bmp, src, dst, paint);
However, when using the canvas from PdfDocument.Page.getCanvas, this canvas will not downscale the bitmap, it will just squeeze it into a smaller zone. Instead you should do something like this:
// Scale bitmap : filter = false since we are always downSampling
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight,
false); // filter=false if downscaling, true if upscaling
canvas.drawBitmap(scaledBitmap, null, dst, paint);
scaledBitmap.recycle();
This is embedded in Android so it is much easier than using a third-party library. (The above was tested on a Marshmallow platform)
This seems to just be a bug in PdfDocument. The PDF file I created with PdfDocument was 5.6 megabytes. The same document generated through the iOS equivalent was 500K. If I take the Android PDF and run it through Adobe Acrobat's pdf optimization, without compressing any images, the 5.6MB file becomes 350K. They look identical, and I applied no compression in Adobe Acrobat.
In the actual PDF code, the Android image object dictionary is this
<</Type /XObject
/Subtype /Image
/Width 1224
/Height 1584
/ColorSpace /DeviceRGB
/BitsPerComponent 8
/Length 5816448
>>
The PDF from iOS has this dict
<< /Length 8 0 R
/Type /XObject
/Subtype /Image
/Width 1224
/Height 1584
/ColorSpace /DeviceRGB
/SMask 9 0 R
/BitsPerComponent 8
/Filter /FlateDecode >>
I think the problem is the lack of the FlateDecode filter in the Android version. When I run it through the Adobe Acrobat PDF optimizer, it gets the FlateDecode filter.

Google Drive Custom Thumbnail for Standard MIME Type

I have yet another pesky question for people who understand how Google Drive SDK works. On Android platform, I am creating my own custom thumbnails for JPEG image files ( thumbnail is a reduced JPG of the most important detail of the parent image ), The size is a bit non-standard - 384 x 128px, but well within limits stated in the documentation. So, the code goes like this:
// thumbnail
String myThumb = "test.tnl";
Thumbnail tn = new Thumbnail();
tn.setMimeType("image/jpeg");
tn.setImage(Base64.encodeBase64String(myThumb.getBytes()));
// define meta-data
File body = new File();
body.setTitle("test.jpg");
body.setDescription("bla bla");
body.setMimeType("image/jpeg");
body.setThumbnail(tn);
File gooFl = drvSvc.files()
.insert(body, new FileContent("image/jpeg", new java.io.File(test.jpg)))
.execute();
and executes flawlessly (there are more 'body' elements I don't list here) and everything works like a charm. But when I download the image, my thumbnail is gone, replaced by standard Google thumbnail - s220 type.
I did notice the documentation statement:
As with indexable text, Drive automatically generates thumbnails for many common file types. For shortcuts and other file types Drive can not render, you can provide a thumbnail image generated by your application.
Reading it ambiguously, I was hoping that by supplying my own thumbnail to a known MIME type, I will keep Google Drive from generating its standard one, but it probably is not the case. So the question remains. Is there a solution to my problem? Having custom thumbnails for standard "image/jpeg" MIME types? Or is there a work-around, perhaps another custom field I can stick some 10Kb of binary data in? I need the thumbnails in my Android viewer - another app.
Thank you, sean
Google Drive will only use custom thumbnails for non-standard MIME types, so you can't override the one for jpeg. Your app can use a custom file property to store and read proprietary data:
https://developers.google.com/drive/properties

Categories

Resources