Skip to content

Commit

Permalink
Add screenshot.py example and off-screen rendering section in Tutorial.
Browse files Browse the repository at this point in the history
Add screenshot.py, a simple example of off-screen rendering (#287).
  • Loading branch information
cztomczak committed Apr 22, 2017
1 parent 804a0a1 commit b1f5348
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 19 deletions.
11 changes: 7 additions & 4 deletions api/RenderHandler.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Called when the browser wants to move or resize the popup widget.
| Parameter | Type |
| --- | --- |
| browser | [Browser](Browser.md) |
| element_type | int |
| element_type | PaintElementType |
| dirty_rects | list[[x,y,width,height],[..]] |
| paint_buffer | [PaintBuffer](PaintBuffer.md) |
| width | int |
Expand All @@ -145,9 +145,12 @@ of rectangles in pixel coordinates that need to be repainted. |buffer| will
be |width|*|height|*4 bytes in size and represents a BGRA image with an
upper-left origin.

`paintElementType` constants in the cefpython module:
* PET_VIEW
* PET_POPUP
**Important:** Do not keep reference to |paint_buffer| after this
method returns.

`PaintElementType` enum:
* cef.PET_VIEW
* cef.PET_POPUP


### OnCursorChange
Expand Down
159 changes: 153 additions & 6 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ on Chromium in a Python application. You can also use it to
create a HTML 5 based GUI in an application that can act as
a replacement for standard GUI toolkits such as wxWidgets,
Qt or GTK. With this tutorial you will learn CEF Python
basics. This tutorial will discuss the two basic examples:
[hello_world.py](../examples/hello_world.py)
and [tutorial.py](../examples/tutorial.py). There are many
basics. This tutorial will discuss the three featured examples:
[hello_world.py](../examples/hello_world.py),
[tutorial.py](../examples/tutorial.py)
and [screenshot.py](../examples/screenshot.py). There are many
more examples that you can find in the [Examples-README.md](../examples/Examples-README.md)
file, but these examples are out of scope for this tutorial.

Expand All @@ -23,6 +24,7 @@ Table of contents:
* [Javascript integration](#javascript-integration)
* [Javascript exceptions and Python exceptions](#javascript-exceptions-and-python-exceptions)
* [Plugins and Flash support](#plugins-and-flash-support)
* [Off-screen rendering](#off-screen-rendering)
* [Build executable](#build-executable)
* [Support and documentation](#support-and-documentation)

Expand All @@ -46,9 +48,9 @@ The hello_world.py example's source code will be analyzed line
by line in the next section of this Tutorial.

This tutorial in its further sections will also reference the
tutorial.py example which will show how to use more advanced
CEF Python features. The tutorial.py example is also available
in the examples/ directory.
tutorial.py and screenshot.py examples which will show how to
use more advanced CEF Python features. All these examples are
available in the examples/ root directory.


## Hello world
Expand Down Expand Up @@ -470,6 +472,151 @@ For the old CEF Python v31 release instructions for enabling Flash
support are available on Wiki pages.


## Off-screen rendering

Off-screen rendering, in short OSR, also known as windowless
rendering, is a method of rendering pages into a memory buffer
without creating an actual visible window. This method of
rendering has its uses, some pluses and some minuses. Its main
use is so that web page rendering can be integrated into apps
that have its own rendering systems and they can draw web browser
contents only if they are provided a pixel buffer to draw. CEF Python
provides a few examples of integrating CEF off-screen rendering
with frameworks such as Kivy, Panda3D and Pygame/PyOpenGl.

In this tutorial it will be discussed [screenshot.py](../examples/screenshot.py)
example which is a very basic example of off-screen rendering.
This example creates a screenshot of a web page with viewport
size set to 800px width and 5000px height which is an equivalent
of scrolling down page multiple times, but you get all this in
one single screenshot.

Before running this script you must install PIL image library:

```text
pip install PIL
```

This example accepts optional arguments so that you can change
url and viewport size. Example usage:

```text
python screenshot.py
python screenshot.py https://github.com/cztomczak/cefpython 1024 5000
python screenshot.py https://www.google.com/ 800 600
```

Let's discuss code in this example.

To be able to use off-screen rendering mode in CEF you have to set
[windowless_rendering_enabled](../api/ApplicationSettings.md#windowless_rendering_enabled)
option to True, eg.:

```Python
cef.Initialize(settings={"windowless_rendering_enabled": True})
```

Do not enable this value if the application does not use off-screen
rendering as it may reduce rendering performance on some systems.

Another thing that distincts windowed rendering from off-screen
rendering is that when creating browser you have to call SetAsOffscreen
method on the WindowInfo object. Code from the example:

```Python
parent_window_handle = 0
window_info = cef.WindowInfo()
window_info.SetAsOffscreen(parent_window_handle)
browser = cef.CreateBrowserSync(window_info=window_info,
url=URL)
```

Also after creating browser it is required to let CEF know that
viewport size is available and that OnPaint callback may be called
(this callback will be explained in a moment) by calling
WasResized method:

```Python
browser.WasResized()
```

Off-screen rendering requires implementing [RenderHandler](../api/RenderHandler.md#renderhandler-interface)
which is one of client handlers and how to use them was
explained earlier in the tutorial in the [Client handlers](#client-handlers)
section. For basic off-screen rendering it is enough to
implement only two methods: [GetViewRect](../api/RenderHandler.md#getviewrect)
and [OnPaint](../api/RenderHandler.md#onpaint). In the GetViewRect
callback information on viewport size will be provided to CEF:

```Python
def GetViewRect(self, rect_out, **_):
rect_out.extend([0, 0, VIEWPORT_SIZE[0], VIEWPORT_SIZE[1]])
return True
```

In this callback viewport size is returned via |rect_out| which
is of type "list" and thus is passed by reference. Additionally
a True value is returned by function to notify CEF that rectangle
was provided.

In the OnPaint callback CEF provides a [PaintBufer](../api/PaintBuffer.md#paintbuffer-object) object, which is a pixel buffer of the
browser view. This object has [GetIntPointer](../api/PaintBuffer.md#getintpointer)
and [GetString](../api/PaintBuffer.md#getstring) methods. In the
example the latter method is used which returns bytes. The method
name is a bit confusing for Python 3 users, but in Python 2 bytes
were strings and thus the name. Here is the code:

```Python
def OnPaint(self, browser, element_type, paint_buffer, **_):
if element_type == cef.PET_VIEW:
buffer_string = paint_buffer.GetString(mode="rgba",
origin="top-left")
browser.SetUserData("OnPaint.buffer_string", buffer_string)
```

The |element_type| argument can be either of cef.PET_VIEW
(main view) or cef.PET_POPUP (for drawing popup widgets like
`<select>` element). You can see a call to [SetUserData](../api/Browser.md#setuserdata)
which is a helper method for storing custom data associated with
browser. This data is stored for later use when page completes
loading. During loading of a page there are many calls to OnPaint
callback and it is not yet known which call is the last when
loading completes and thus image buffer is stored for later use.

The screenshot example also implements another handler named
[LoadHanadler](../api/LoadHandler.md#loadhandler-interface)
and two of its callbacks: [OnLoadingStateChange](../api/LoadHandler.md#onloadingstatechange)
and [OnLoadError](../api/LoadHandler.md#onloaderror). The
OnLoadingStateChange callbacks notifies when web page loading
completes and OnLoadError callback notifies if loading of
page failed. When loading succeeds a function save_screenshot()
is called which retrieves image buffer that was earlier stored
in the browser object and then uses PIL image library to save
it as a PNG image.

At the end, it is worth noting that there is yet an another
option for off-screen rendering named [windowless_frame_rate](../api/BrowserSettings.md#windowless_frame_rate)
(can be passed to [CreateBrowserSync](../api/cefpython.md#createbrowsersync)),
but is not used by this example. It sets the maximum rate
in frames per second (fps) that OnPaint callback will be called

Currently CEF requires a window manager on Linux even in off-screen
rendering mode. On systems without screen or any input you can use
something like [Xvfb](https://en.wikipedia.org/wiki/Xvfb) which
performs all graphical operations in memory without showing any
screen output. Pure headless mode is currently not supported in
CEF, but that may change in the future. CEF currently depends on
X11 window manager, but there are plans to support alternative
window managers by adding Ozone support - [upstream issue #1989](https://bitbucket.org/chromiumembedded/cef/issues/1989/linux-add-ozone-support-as-an-alternative).

The screenshot.py example is just a basic showcase of off-screen
rendering features. That screenshot feature should also be possible
to implement using windowed rendering using a normal GUI window,
but a hidden one with height set to some very big value that it
wouldn't fit on screen. You could then render contents of this
window to an image. For example in Qt you can do this by using
QImage/QPainter classes along with a call to QWidget.render().

## Build executable

There are no official examples for building executable using
Expand Down
20 changes: 14 additions & 6 deletions examples/Examples-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,22 @@ python hello_world.py
## Supported examples

Examples provided in the examples/ root directory are actively
maintained:
maintained. If there are any issues in examples read top comments
in sources to see whether this is a known issue with available
workarounds.

- [hello_world.py](hello_world.py): basic example, doesn't require any
**Featured**

- [hello_world.py](hello_world.py) - Basic example, doesn't require any
third party GUI framework to run
- [tutorial.py](tutorial.py): example from [Tutorial](../docs/Tutorial.md)
- [tutorial.py](tutorial.py) - Example from [Tutorial](../docs/Tutorial.md)
- [screenshot.py](screenshot.py) - Example of off-screen rendering mode
to create a screenshot of a web page. Also referenced in Tutorial
in the [Off-screen rendering](../docs/Tutorial.md#off-screen-rendering)
section.

**Embedding using various GUI frameworks**

- [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/)
library (GTK 2)
- [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject)
Expand All @@ -38,9 +49,6 @@ maintained:
- [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/)
toolkit

If there are any issues in examples read top comments in sources
to see whether this is a known issue with available workarounds.

**Unit tests**

There are also available unit tests and its usage of the API can
Expand Down
Loading

0 comments on commit b1f5348

Please sign in to comment.