13

How do can you generate a bitmap from HTML in Android?

Can the WebView be used for this or is there a better approach (like maybe using the WebView rendering engine directly)? How?

I would like to implement the following method...

public Bitmap toBitmap(Context context, String html, Rect rect);

...where html is the html to render and rect is the frame of the desired bitmap.

hpique
  • 119,096
  • 131
  • 338
  • 476
  • You might consider adding a few hundred more words explaining what "generate dynamic bitmaps from HTML" means. – CommonsWare Jan 08 '11 at 13:49
  • In a nutshell I want to generate a bitmap from rendered HTML. I removed "dynamic" and rephrased in case it was confusing. – hpique Jan 08 '11 at 15:12
  • FWIW, here is my attempt at a solution: http://stackoverflow.com/a/41354684/6684508 – zll Dec 28 '16 at 02:47

5 Answers5

18

A synchronous method that generates a bitmap from an HTML string using a WebView, and can be used within an AsyncTask:

public Bitmap getBitmap(final WebView w, int containerWidth, int containerHeight, final String baseURL, final String content) {
    final CountDownLatch signal = new CountDownLatch(1);
    final Bitmap b = Bitmap.createBitmap(containerWidth, containerHeight, Bitmap.Config.ARGB_8888);
    final AtomicBoolean ready = new AtomicBoolean(false); 
    w.post(new Runnable() {

        @Override
        public void run() {
            w.setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView view, String url) {
                    ready.set(true);
                }
            });
            w.setPictureListener(new PictureListener() {
                @Override
                public void onNewPicture(WebView view, Picture picture) {
                    if (ready.get()) {
                        final Canvas c = new Canvas(b);
                        view.draw(c);
                        w.setPictureListener(null);
                        signal.countDown();
                    }
                }
            });
            w.layout(0, 0, rect.width(), rect.height());
            w.loadDataWithBaseURL(baseURL, content, "text/html", "UTF-8", null);
        }});
    try {
        signal.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return b;
}

It has some limitations, but it's a start.

hpique
  • 119,096
  • 131
  • 338
  • 476
7

You can use the draw method to let it draw in a Bitmap of your choice. I made an example, don't forget internet and external storage rights of your manifest:


public class MainActivity extends Activity {
    private WebView mWebView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mWebView = new WebView(this);
        setContentView(mWebView);
        mWebView.loadUrl("http://tea.ch");
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
      if (keyCode != KeyEvent.KEYCODE_BACK) return super.onKeyDown(keyCode, event);
      Bitmap bm = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888);
      Canvas c = new Canvas(bm);
      mWebView.draw(c);
      OutputStream stream = null;
      try {
        stream = new FileOutputStream(Environment.getExternalStorageDirectory() +"/teach.png");
        bm.compress(CompressFormat.PNG, 80, stream);
        if (stream != null) stream.close();
      } catch (IOException e) {
      } finally {
        bm.recycle();
      }
return super.onKeyDown(keyCode, event); } }
Phyrum Tea
  • 2,623
  • 1
  • 19
  • 20
  • So there's no way to skip the WebView and directly use its rendering engine? – hpique Jan 08 '11 at 15:13
  • It's a view, you can only keep an instance of it as a rendering engine. You have to set the size for rendering as Laoyout params yourself like: LayoutParams params = new LayoutParams(500,500); mWebView.setLayoutParams(params); mWebView.layout(0, 0, 500, 500); – Phyrum Tea Jan 08 '11 at 16:24
  • I just tested it, you only need to set mWebView.layout(0, 0, 500, 500); – Phyrum Tea Jan 08 '11 at 16:26
  • +1 for testing. :) Will try and accept the answer if it works. – hpique Jan 08 '11 at 17:47
  • 1
    Unfortunately this solution only works inside an activity. Do you know how it could be implemented in a worker thread? – hpique Jan 09 '11 at 16:05
  • I haven't tested it in a separate thread yet. But could it be that the page isn't finishing loading? You have to create a webview-client and listen to the events. – Phyrum Tea Jan 10 '11 at 11:48
0

This example shows how to capture webView content last picture (it waits until webview complete rendering picture), it is an example of convert HTML to PNG using Android

Activity Code

public class HtmlViewer extends Activity {
private String HTML; 
private Context ctx;
private Picture pic = null;
private int i=0; suppose this is the last pic
private int oldi = 0; 
private Timer myTimer; // timer for waiting until last picture loaded
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_html_viewer);
    Intent intent = getIntent();
    HTML = intent.getStringExtra("HTML");
    ctx = this;
    WebView wv = (WebView)findViewById(R.id.webView1);
    wv.setPictureListener(new PictureListener(){

        public void onNewPicture(WebView view, Picture picture) {
            Log.w("picture", "loading.." + String.valueOf(view.getProgress()));
            pic = picture;
            i++;


            }

        });
    wv.loadData(HTML, "text/html; charset=utf-8", null);

    wv.setWebViewClient(new WebViewClient()     
    {
        public void onPageFinished(WebView wv, String url)
        {
            Picture p = wv.capturePicture();
            myTimer = new Timer();
            myTimer.schedule(new TimerTask() {          
                @Override
                public void run() {
                    if (i > oldi)
                        oldi = i;
                        else
                            if (i != 0)
                        {
                            Log.w("picture", "finished");
                            cancel();
                            Picture picture = pic;

                            Log.w("picture", "onNewPicture- Height"+ picture.getHeight());
                            Log.w("picture", "onNewPicture- Width"+ picture.getWidth());

                            File sdCard = Environment.getExternalStorageDirectory();
                            if (picture != null)
                            {
                                Log.w("picture", " P OK");
                            Bitmap image = Bitmap.createBitmap(picture.getWidth(),picture.getHeight(), Config.ARGB_8888);
                            Canvas canvas = new Canvas(image);
                            picture.draw(canvas);
                            Log.w("picture", "C OK");

                            if (image != null) {
                                Log.w("picture", "I OK");
                                ByteArrayOutputStream mByteArrayOS = new ByteArrayOutputStream();
                                image.compress(Bitmap.CompressFormat.PNG, 90, mByteArrayOS);
                                try {
                                    File file = new File(sdCard, "AccountView.PNG");
                                    FileOutputStream fos = new FileOutputStream(file);
                                    fos.write(mByteArrayOS.toByteArray());
                                    fos.flush();
                                    fos.close();                                        
                                    Log.w("picture", "F OK " + String.valueOf(mByteArrayOS.size()) + " ? " + String.valueOf(file.length()));
                                    Intent sharingIntent = new Intent(Intent.ACTION_SEND);
                                    Uri screenshotUri = Uri.fromFile(file);                      
                                    sharingIntent.setType("image/png");
                                    sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
                                    startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.ACCOUNT_VIEW_TITLE)));                      
                                    ((Activity)ctx).finish();
                                } catch (FileNotFoundException e) {
                                    e.printStackTrace();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }               
                            }

                        }
                }

            }, 0, 1000);

            Log.w("picture", "done");

            loadcompleted = true;
        }
    });



}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_html_viewer, menu);
    return true;
}



}

Layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".HtmlViewer" >

<WebView
    android:id="@+id/webView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Sankumarsingh
  • 9,889
  • 11
  • 50
  • 74
0

Why not use the WebView method : capturePicture() which returns a Picture and is available since API level 1 ?

It returns a picture of the entire document.

You could then crop the result with your rectangle and save the bitmap from there.

Yahel
  • 8,522
  • 2
  • 24
  • 32
  • Note that `capturePicture()` was deprecated in API level 19: http://developer.android.com/reference/android/webkit/WebView.html#capturePicture() – drmrbrewer Nov 23 '15 at 16:19
0

This is a good library that can be used to convert any HTML content to bitmap.

It supports both URL and HTML String

https://github.com/iZettle/android-html2bitmap

Ismail Iqbal
  • 2,774
  • 1
  • 25
  • 46