I am having a problem with Android graphics. I am doing game development and need to display images some of which have color gradients. My problem is that when I load bitmap images (in png format) with gradients the images display with a banding artifact. And this is on Android 4. I researched numerous posts relating to this issue, and tried numerous solutions including:
Dithering the image on input
BitmapFactory.Options factoryOptions =
new BitmapFactory.Options();
factoryOptions.inDither = true;
...
background = BitmapFactory.decodeResource( resources, R.drawable.game_page_background, factoryOptions );
Loading the image from "res/raw" instead of "res/drawable"
Verifying the pixel format of my display as: Bitmap Config ARGB_8888
Loading the image from the assets directory using an input stream.
I assumed Solutions 2 and 4 should have prevented Android image "optimization" which (again I assume) is producing the artifact. But none of the solutions work. The artifact remains no matter how I load the bitmap. In the end I had to do a horrible workaround which was to bake noise into the image using photoshop. Obviously, this is a horrible workaround.
Can anyone from this community offer any further advice as to how to get bitmap images with gradients to render smoothly in Android without the banding artifact?
The following code frags show how I've generated these test images...
CODE FRAG **
...
InputStream is = null;
try
{
is = ((Activity)gameMngr).getAssets().open("test_background_3.png");
}
catch( IOException ioe)
{
Log.d(TAG, "TEST CODE: Unable to open resources. ");
}
this.background = BitmapFactory.decodeStream(is);
...
// ELSEWHERE
...
canvas.drawBitmap( this.background, 0, 0, null );
...
END FRAG **
I think you can create a copy of your from decodeStream and specify the Bitmap COnfig
Here is the revised code frag of yours:
InputStream is = null;
try
{
is = ((Activity)gameMngr).getAssets().open("test_background_3.png");
}
catch( IOException ioe)
{
Log.d(TAG, "TEST CODE: Unable to open resources. ");
}
this.background = BitmapFactory.decodeStream(is);
//create copy and specify the config of the bitmap, setting to true make the bitmap
//mutable.
Bitmap newBtmp = this.background.copy(Bitmap.Config.ARGB_8888, true);
//use the newBtmp object
canvas.drawBitmap( newBtmp, 0, 0, null );
...
If this post helps you, please make this post as an answer.
Thanks.
Related
Since some point i get this error. On API21- all works file, on API24 all ok, API29+ broken, didn't tested other, may be someone know what happened or changed?
Here simple is code:
#Nullable
Bitmap getImage() {
if (!isMediaFileExist()) return null;
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
try {
mediaMetadataRetriever.setDataSource(getMediaFile().getFullPath());
} catch (RuntimeException e) {
return null;
}
byte[] imageByte = mediaMetadataRetriever.getEmbeddedPicture(); #MIN API 8
if (imageByte == null) return null;
return BitmapFactory.decodeByteArray(imageByte, 0, imageByte.length);
}
Source function bigger, but this cuted part isn't working too.
Bitmap assigns to ImageView by standard way.
In result I see some part of picture and grey background.
That has happened with all mp3. On API21 still all looks good.
I've tried for test to do BitmapFactory.decodeByteArray(imageByte, 0, imageByte.length-16384); for example and have same part of image with grey background. Looks like lib encounter error. But on PC, in photoShop, Paint and other picture looks good. Headers in HEX editor looks good to.
Inside MP3 cover are standard JPEG, 53Kb, about 300x300 px, but size doesn't matter I think, it happened with all mp3
Didn't find solution for MediaMetadataRetriever
moved to https://github.com/wseemann/FFmpegMediaMetadataRetriever
works fine. Only one problem +16Mb of apk
Is there any way to get a high resolution screen shot of a certain view in an activity.
I want to convert html content of my webview to PDF. For that I tried to take screen shot of the webview content and then converted it to PDF using itext. The resulted PDF is not in much more clarity.
My code:
protected void takeimg() {
Picture picture = mWebView.capturePicture();
Bitmap b = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
picture.draw(c);
// byte[] bt = b.getNinePatchChunk();
// Bitmap b;
// View v1 = mWebView.getRootView();
// v1.setDrawingCacheEnabled(true);
// b = Bitmap.createBitmap(v1.getDrawingCache());
// v1.setDrawingCacheEnabled(false);
FileOutputStream fos = null;
try {
File root = new File(Environment.getExternalStorageDirectory(),
"Sample");
if (!root.exists()) {
root.mkdir();
}
String sdcardhtmlpath = root.getPath().toString() + "/"
+ "temp_1.png";
fos = new FileOutputStream(sdcardhtmlpath);
// fos = openFileOutput("samsp_1.jpg", MODE_WORLD_WRITEABLE);
if (fos != null) {
b.compress(Bitmap.CompressFormat.PNG, 100, fos);
// fos.write(bt);
fos.close();
}
} catch (Exception e) {
Log.e("takeimg", e.toString());
e.printStackTrace();
}
}
protected void pdfimg() {
Document mydoc = new Document(PageSize.A3);
try {
File root = new File(Environment.getExternalStorageDirectory(),
"Sample");
if (!root.exists()) {
root.mkdir();
}
String sdcardhtmlpath = root.getPath().toString() + "/";
mydoc.setMargins(0, 0, 0, 0);
PdfWriter.getInstance(mydoc, new FileOutputStream(sdcardhtmlpath
+ PDFfilename));
mydoc.open();
Image image1 = Image.getInstance(sdcardhtmlpath + "temp_1.jpg");
image1.scalePercent(95f);
mydoc.add(image1);
// mydoc.newPage();
mydoc.close();
} catch (Exception e) {
Log.e("pdi name", e.toString());
}
}
Update: See Edit 3 for an answer to op's original question
There are two options:
Use a library to convert the HTML to PDF. This is by far the best option, since it will (probably) preserve text as vectors.
Get a high resolution render of the HTML and save it as a PNG (not PDF surely!).
For HTML to PDF, wkhtmltopdf looks like a good option, but it relies on Qt which you can't really use on Android. There are some other libraries but I doubt they do the PDF rendering very well.
For getting a high-res webview, you could try creating your own WebView and calling onMeasure(...) and onLayout(...) and pass appropriate parameters so the view is really big. Then call onDraw(myOwnCanvas) and the webview will draw itself to your canvas, which can be backed by a Bitmap using Canvas.setBitmap().
You can probably copy the state into the new WebView using something like
screenshotterWebview.onRestoreInstanceState(mWebView.onSaveInstanceState());
Orrr it may even be possible to use the same WebView, just temporarily resize it to be large, onDraw() it to your canvas, and resize it back again. That's getting very hacky though!
You might run into memory issues if you make it too big.
Edit 1
I thought of a third, exactly-what-you-want option, but it's kind of hardcore. You can create a custom Canvas, that writes to a PDF. In fact, it is almost easy, because underlying Canvas is Skia, which actually includes a PDF backend. Unfortunately you don't get access to it on Android, so you'll basically have to build your own copy of it on Android (there are instructions), and duplicate/override all the Canvas methods to point to your Skia instead of Androids. Note that there is a tempting Picture.writeToStream() method which serializes the Skia data, but unfortunately this format is not forwards or backwards compatible so if you use it your code will probably only work on a few versions of Android.
I'll update if/when I have fully working code.
Edit 2
Actually it is impossible to make your own "intercepting" Canvas. I started doing it and went through the tedious process of serializing all function calls. A few you can't do because they are hidden, but those didn't look important. But right at the end I came to serializing Path only to discover that it is write-only. That seems like a killer to me, so the only option is to interpret the result of Picture.writeToStream(). Fortunately there are only two versions of that format in use, and they are nearly identical.
Edit 3 - Really simple way to get a high resolution Bitmap of a view
Ok, it turns out just getting a high res bitmap of a view (which can be the entire app) is trivial. Here is how to get double resolution. Obviously all the bitmaps look a bit crap, but the text is rendered at full resolution:
View window = activity.getWindow().getDecorView()
Canvas bitmapCanvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(window.getWidth()*2, window.getHeight()*2, Bitmap.Config.ARGB_8888);
bitmapCanvas.setBitmap(bitmap);
bitmapCanvas.scale(2.0f, 2.0f);
window.draw(bitmapCanvas);
bitmap.compress(Bitmap.CompressFormat.PNG, 0, myOutputStream);
Works like a charm. I've now given up on getting a PDF screenshot with vector text. It's certainly possible, but very difficult. Instead I am working on getting a high-res PSD where each draw operation is a separate layer, which should be much easier.
Edit 4
Woa this is getting a bit long, but success! I've generated an .xcf (GIMP) and PDF where each layer is a different canvas drawing operation. It's not quite as fine-grained as I was expecting, but still, pretty useful!
Actually my code just outputs full-size PNGs and I used "Open as layers..." and "Autocrop layer" in GIMP to make these files, but of course you can do that in code if you like. I think I will turn this into a blog post.
Download the GIMP or Photoshop demo file (rendered at 3x resolution).
When you capture the view, just screen bound will capture ( due to control weight and android render pipeline ).
Capturing screenshot for converting to PDF is tricky way. I think two way is more reasonable solutions.
Solution #1
Write a parser ( it's simple ) to convert webview content ( that is HTML ) to iText format.
You can refer to this article for more information.
http://www.vogella.com/articles/JavaPDF/article.html
Also to write a parser you can use REGEX and provide your own methods like parseTable, parseImage, ...
Solution #2 Internet Required
Provide a URL ( or webservice ) to convert HTML to PDF using PHP or C# that has a lot of nice libraries. Next you can send download link to the Client ( Android Device ).
So you can also dynamically add some Tags, Banners, ... to the PDF from server side.
Screen Shot is nothing but picture of your device display which usually depend upon your phone absolute pixels, if your phone is 480x800 screen shot will be same and generally applicable for all scenarios.
Sure, Use this:
Bitmap bitmap;
View v1 = MyView.getRootView();
v1.setDrawingCacheEnabled(true);
bitmap = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Here MyView is the View you need a screenshot of.
Having kind of an issue with initiating 9patch drawables from input streams. I need to skin my app and need to download skin elements and images from a web service.
Sought through a reasonable amount of resources both in SO and android dev guides, but none seem to work for me.
Setting a drawable from a resource does handle 9patch properly so logically the smarts to do so is there, but for some reason the following code, which I derived from the android sources itself, fails to handle 9patch properly
Rect pad = new Rect();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScreenDensity = DisplayMetrics.DENSITY_DEFAULT;
Bitmap bm = BitmapFactory.decodeResourceStream(resources, typedValue, new FileInputStream(path), pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
if (np == null || !NinePatch.isNinePatchChunk(np)) {
np = null;
pad = null;
}
if (np != null) {
drawable = new NinePatchDrawable(resources, bm, np, pad, path);
} else {
drawable = new BitmapDrawable(resources, bm);
}
}
I have changed the input source to be the one of my files (FileInputStream(path)), in the android sources it is the input source initiated on resource images.
This code always returns BitmapDrawable even if the input image is a 9-patch.
Does anyone succeed actually getting this functionality working?
I'd appreciate any help or hint towards a solution.
Thank you in advance.
Okay, the solution is that there's no solution here, because 9 patch requires nine patch chunk as an array which is being generated at compile time. Obviously we do not have a compile phase when loading images from a web resource.
To Android engineers - maybe future release of android SDKs will be able to generate the nine patch chunk at run time.
I've created this gist to create 9patches at runtime: https://gist.github.com/4391807
I like to use the assets folder instead of the drawable folder (if it's not a nine-patch) because I can use multiple folders there. However the method I use to get an drawable required the cpu to do quiet a lot. For example: after adding 10 ImageViews need 10% CPU (I am using Android Assistent and Samsung TouchWiz TaskManager). I haven't notice it while I was writing a game. And now this game needs 40-100% CPU even if it isn't in the foreground.
That's the method I use to create an drawable:
public BitmapDrawable readAsset(path){
try{
inputStream = assetManager.open(path);
//get the Bitmap
desiredImg = BitmapFactory.decodeStream(inputStream, null, opts);
//resize for other screen sizes (scale is calculated beforehand)
scaleX =(int)(desiredImg.getWidth()/scale);
scaleY = (int)(desiredImg.getHeight()/scale);
//Bitmap to get config ARGB_8888 (createScaledBitmap returns RGB_565 => bad quality especially in gradients)
//create empty bitmap with Config.ARGB_8888 with the needed size for drawable
Bitmap temp = Bitmap.createBitmap(scaleX, scaleY, Config.ARGB_8888);
//Canvas to draw desiredImg on temp
Canvas canvas = new Canvas(temp);
canvas.drawBitmap(convert, null, new Rect(0, 0, scaleX, scaleY), paint);
//Convert to BitmapDrawable
BitmapDrawable bitmapDrawable=new BitmapDrawable(temp);
bitmapDrawable.setTargetDensity(metrics);
inputStream.close();
return bitmapDrawable;
}catch (Exception e) {
Log.d(TAG, "InputStream failed: "+e.getMessage());
}
return null;
}
The only thing I do in the app is adding some ImageViews in a RelativeLayout with this method:
private void addImageToContainer(int paddingLeft, int paddingTop) {
ImageView imageView = new ImageView(this);
imageView.setImageDrawable(assetReader.readAsset("test.jpg"));
imageView.setPadding(paddingLeft, paddingTop, 0, 0);
container.addView(imageView);
}
Probably the best thing for you to do would be to profile the execution with traceview, as this will give you a full understanding of where your app is spending most of its execution time. Then you can focus on optimizing that specific piece of code.
Just an educated guess, but I have a feeling that the majority of the wasted execution is not because you are pulling the images out of assets/ instead of resources, but all the scaling work being done afterwards (and from the looks of it, this is all being done on the main thread, so there's no concurrency to speak of).
I might recommend trying to leverage some of the BitmapFactory.Options (Docs link) available to you when you decode the asset. In particular, you should be able to do all the scaling you need with a combination of the inScaled, inDensity, and inTargetDensity options. If you pass these to your decodeStream() method, you could likely remove all the subsequent code used to resize the image before returning.
I am currently building this Android application, where I will be taking a screenshot of a "TableLayout" and then emailing it as an attachment. Here is the part of the code which takes the screenshot.
However, when I try to attach the file, using the following code, it says that "File Size Too Large for Attachment". Can anyone suggest any other measures that I can take, apart from Bitmap.Compress, in order to make my file size even smaller? Thanks in advance!
private void getScreen()
{
View content = findViewById(R.id.TransactionLog);
content.setDrawingCacheEnabled(true);
content.buildDrawingCache(true);
Bitmap bitmap = Bitmap.createBitmap(content.getDrawingCache());
content.setDrawingCacheEnabled(false); // clear drawing cache
File file = new File(Environment.getExternalStorageDirectory() +
File.separator + "whatever2.png");
try
{
file.createNewFile();
FileOutputStream ostream = new FileOutputStream(file);
bitmap.compress(CompressFormat.PNG, 0, ostream);
ostream.flush();
ostream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
Try using Bitmap.createScaledBitmap:
public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int
dstHeight, boolean filter)
Since: API Level 1
Creates a new bitmap, scaled from an existing bitmap.
Parameters
src - The source bitmap.
dstWidth - The new bitmap's desired width.
dstHeight - The new bitmap's desired height.
filter - true if the source should be filtered.
FYI, the quality parameter passed to compress does not affect the file size when you are using CompressFormat.PNG. Try using CompressFormat.JPEG instead, then try different quality values.
Alternatively, try this:
http://thinkandroid.wordpress.com/2009/12/25/resizing-a-bitmap/
Have you tried use a higher value for the quality parameter? (currently you're using 0 and it could go up to 100).
I suggest trying 80.
boolean success = bitmap.compress(CompressFormat.PNG, 80, ostream);
Don't forget to test if it was successful (log the return value of that method).
You can also try to use another format (jpeg?).
To make things simpler, I suggest you to try to save it to the sdcard first and check if the size is something you'd be expecting. You might have some problem on the code that sends the email or it might not let you send large attachments.
How big is your image? An image made from an app, saved as a png, assuming its not a screenshot of a 'picture', should be pretty small. FAR smaller than what email should be able to accept, unless there's an arbitrarily small attachment size.
If what's in your table is an image, or has quite a bit of variance, you might consider jpeg instead of png. Otherwise, my guess is something else is going on.