In my app the user manipulates a image, rotates, pinch, zoom etc. This is make within onDraw() using canvas.translate(), canvas.rotate() etc. Of course the image is big and after the manipulation of the image the user performs interactions with other Views. The thing is I don't want to redraw the whole big image over and over again only the visible part. So I'd like, after the user finishes operations, SAVE the current VISIBLE image on the screen to memory/bitmap/cache and redraw only that.
How can I achieve this ?
In the constructor of your custom View, enable the drawing cache like this: setDrawingCacheEnabled(true);. Then whenever you want to save the state of your Canvas you can call getDrawingCache(), which returns the desired Bitmap.
Documentation: getDrawingCache()
In your on draw initialize your Canvas with a bitmap.
keep a reference of this bitmap and save it whenever required.
I've found a really great article about what I needed HERE
To kepp thing clear, first I need to create a bitmap with the size of my View, then create a canvas for it, draw on canvas and then save it.
public void saveScreenshot() {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.draw(myGiganticImage)
File file = new File(context.getFilesDir() + "/cache.jpg");
FileOutputStream fos;
try {
fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.close();
cachedBackground = BitmapFactory.decodeFile(context.getFilesDir() + "/cache.jpg"
invalidate()
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
I simply call this on onDraw() when I need it. It will save a bitmap on internal memory which I can use to draw, instead of the big picture.
Following may help you :
(1)
View v = view.getRootView();
v.setDrawingCacheEnabled(true);
Bitmap b = v.getDrawingCache();
(2) This is a similar to another question at How to capture the android device screen content?
(3) You can try using this library
http://code.google.com/p/android-screenshot-library/ It introduces an Android Screenshot Library (ASL) which enables to programmatically capture screenshots from Android devices without requirement of having root access privileges.
Related
I am trying to solve problem when I get bitmap from view (with bad quality of childviews) and store it as PNG. There are some taken pictures using this code:
Bitmap b = Bitmap.createBitmap(
Math.round(General.getDisplayWidthPx()), Math.round(General.getDisplayHeightPx()), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas( b );
wallFrame.invalidate();
wallFrame.draw( canvas );
FileOutputStream out = new FileOutputStream(file);
b.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
When the childview is small the quality of childview is terrible.
But when it's larger the quality is better.
As you can see the smaller the picture, the lower the quality. I also tried deprecated methods:
wallFrame.setDrawingCacheEnabled(true);
Bitmap b = wallFrame.getDrawingCache();
wallFrame is ZoomLayout which extends RelativeLayout. Child views in wallFrame are SubScalingImageView which extends View. I think it's not caused by SubScalingImageView I've tried standard ImageView and result was the same. When I take screenshot of app, the result is better.
Pictures are loaded from user storage using Uri (so not from drawable). SubscalingImageView has own decoders. In app the quality of pictures is perfect.
I think problem is somewhere in view.draw(canvas). But I have no idea how to edit it to make it work.
I appreciate every idea. Thank you!
how to make a screenshot (preview) without using an emulator (or device)?
I need any quick mechanism in order to take a bunch of screenshots (png) for different screen sizes/layouts
I'm not sure any of the layout stuff will work in just a JVM. So would need a headless emulator, emulator or Device to have a screen to screenshot. Robo Electric might allow to to run the code without emulator or Device.
Then you can get layout to draw in to a Bitmap backed Canvas and then save the bitmap to file, this does require you to run the code,
Note this will save the whole layout including any off screen elements.
e.g. A scollview will normal be made to fit the screen, so it will only show what is on screen at the time (same goes for things like recyclerview)
But for things like TableLayouts inside of a scrollview you will get the whole TableLayout including off screen elements
Code:
tableLayout = findViewById(R.id.table);
Canvas bitmapCanvas = new Canvas();
// Double the bitmap size and scale the Canvas to get a better resolution picture
// (Basically doubling the virtual screen resolution (Optional)
Bitmap bitmap = Bitmap.createBitmap(tableLayout.getWidth()*2, tableLayout.getHeight()*2, Bitmap.Config.ARGB_8888);
bitmapCanvas.setBitmap(bitmap);
bitmapCanvas.scale(2.0f, 2.0f);
tableLayout.draw(bitmapCanvas);
// uri is from SAF in this instance but any fileOutputStream can be used
try{
ParcelFileDescriptor pfd =
getApplicationContext().getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(
pfd.getFileDescriptor());
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
// PNG is a lossless format, the compression factor (100) is ignored
fileOutputStream.close();
pfd.checkError();
pfd.close();
} catch (Exception e) {
Log.e("N", e.toString());
}
Update:
If they view has not been laid out on a screen, then you can do it manually instead of findViewById
e.g.
TableLayout tableLayout = new TableLayout();
tableLayout.setLayoutParams(new TableLayout.LayoutParams(TabLayout.LayoutParams.WRAP_CONTENT,
TabLayout.LayoutParams.WRAP_CONTENT));
tableLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
tableLayout.layout(0, 0, tableLayout.getMeasuredWidth(), tableLayout.getMeasuredHeight());
But I still think that without something like Robo Electric providing all the "Android" classes to a standard JVM even this won't work.
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.
So I have an animation that is being created by having a bunch of bitmaps drawn in the onDraw() method of a custom view. There is an update thread that calls a method in the custom view that changes the positions of the bitmaps to be drawn by the onDraw() method. What I would like to do is to save the bitmap created each time the update thread is finished so that i can create a gif from the bitmaps that i save.
I found the below code to save a png from a bitmap stored in memory to the SD card and that works with a stored bitmap but I'm having trouble with getDrawingCache():
public void saveView(){
if(counter < 200){
try {
counter++;
System.out.println("Counter : " + counter);
File file = new File(path, "star"+counter+".png");
file.delete();
OutputStream fOut = new FileOutputStream(file);
buildDrawingCache();
getDrawingCache().compress(Bitmap.CompressFormat.PNG, 100, fOut);
destroyDrawingCache();
fOut.flush();
fOut.close();
MediaStore.Images.Media.insertImage(context.getContentResolver(),file.getAbsolutePath(),file.getName(),file.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Doing this essentially does two things right now:
1) It takes about 50 images and saves them to the sd card. # around 50 it causes the heap to get to large (i guess destroyDrawingCache() is unable to actually finish due to this is in a seperate thread)
2) In the pictures taken, you can see the scan lines from the buffer updating because I'm taking from a buffer that gets updated.
It would seem the getDrawingCache calls onDraw() as well which is why I can't have this on the UI thread in the onDraw itself.
If this is possible please help.
You should not call getDrawingCache() from outside the UI thread. That's why the bitmaps you get have partially updated scan lines. Call saveView() directly from onDraw(). Only the file operations may be run on separate threads after making a clone of the cached bitmap.
Note: You can call setDrawingCacheEnabled(true)? so that you don't have to call buildDrawingCache() and destroyDrawingCache(). Also if your device has hardware acceleration is turned on, you'll have to call setLayerType(LAYER_TYPE_SOFTWARE, null).
Another solution is to get the bitmap by calling View.draw(android.graphics.Canvas) on a new canvas that holds a blank bitmap.
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.