Skip to content

Commit b1f5348

Browse files
committed
Add screenshot.py example and off-screen rendering section in Tutorial.
Add screenshot.py, a simple example of off-screen rendering (cztomczak#287).
1 parent 804a0a1 commit b1f5348

File tree

5 files changed

+386
-19
lines changed

5 files changed

+386
-19
lines changed

api/RenderHandler.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Called when the browser wants to move or resize the popup widget.
129129
| Parameter | Type |
130130
| --- | --- |
131131
| browser | [Browser](Browser.md) |
132-
| element_type | int |
132+
| element_type | PaintElementType |
133133
| dirty_rects | list[[x,y,width,height],[..]] |
134134
| paint_buffer | [PaintBuffer](PaintBuffer.md) |
135135
| width | int |
@@ -145,9 +145,12 @@ of rectangles in pixel coordinates that need to be repainted. |buffer| will
145145
be |width|*|height|*4 bytes in size and represents a BGRA image with an
146146
upper-left origin.
147147

148-
`paintElementType` constants in the cefpython module:
149-
* PET_VIEW
150-
* PET_POPUP
148+
**Important:** Do not keep reference to |paint_buffer| after this
149+
method returns.
150+
151+
`PaintElementType` enum:
152+
* cef.PET_VIEW
153+
* cef.PET_POPUP
151154

152155

153156
### OnCursorChange

docs/Tutorial.md

Lines changed: 153 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ on Chromium in a Python application. You can also use it to
55
create a HTML 5 based GUI in an application that can act as
66
a replacement for standard GUI toolkits such as wxWidgets,
77
Qt or GTK. With this tutorial you will learn CEF Python
8-
basics. This tutorial will discuss the two basic examples:
9-
[hello_world.py](../examples/hello_world.py)
10-
and [tutorial.py](../examples/tutorial.py). There are many
8+
basics. This tutorial will discuss the three featured examples:
9+
[hello_world.py](../examples/hello_world.py),
10+
[tutorial.py](../examples/tutorial.py)
11+
and [screenshot.py](../examples/screenshot.py). There are many
1112
more examples that you can find in the [Examples-README.md](../examples/Examples-README.md)
1213
file, but these examples are out of scope for this tutorial.
1314

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

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

4850
This tutorial in its further sections will also reference the
49-
tutorial.py example which will show how to use more advanced
50-
CEF Python features. The tutorial.py example is also available
51-
in the examples/ directory.
51+
tutorial.py and screenshot.py examples which will show how to
52+
use more advanced CEF Python features. All these examples are
53+
available in the examples/ root directory.
5254

5355

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

472474

475+
## Off-screen rendering
476+
477+
Off-screen rendering, in short OSR, also known as windowless
478+
rendering, is a method of rendering pages into a memory buffer
479+
without creating an actual visible window. This method of
480+
rendering has its uses, some pluses and some minuses. Its main
481+
use is so that web page rendering can be integrated into apps
482+
that have its own rendering systems and they can draw web browser
483+
contents only if they are provided a pixel buffer to draw. CEF Python
484+
provides a few examples of integrating CEF off-screen rendering
485+
with frameworks such as Kivy, Panda3D and Pygame/PyOpenGl.
486+
487+
In this tutorial it will be discussed [screenshot.py](../examples/screenshot.py)
488+
example which is a very basic example of off-screen rendering.
489+
This example creates a screenshot of a web page with viewport
490+
size set to 800px width and 5000px height which is an equivalent
491+
of scrolling down page multiple times, but you get all this in
492+
one single screenshot.
493+
494+
Before running this script you must install PIL image library:
495+
496+
```text
497+
pip install PIL
498+
```
499+
500+
This example accepts optional arguments so that you can change
501+
url and viewport size. Example usage:
502+
503+
```text
504+
python screenshot.py
505+
python screenshot.py https://github.com/cztomczak/cefpython 1024 5000
506+
python screenshot.py https://www.google.com/ 800 600
507+
```
508+
509+
Let's discuss code in this example.
510+
511+
To be able to use off-screen rendering mode in CEF you have to set
512+
[windowless_rendering_enabled](../api/ApplicationSettings.md#windowless_rendering_enabled)
513+
option to True, eg.:
514+
515+
```Python
516+
cef.Initialize(settings={"windowless_rendering_enabled": True})
517+
```
518+
519+
Do not enable this value if the application does not use off-screen
520+
rendering as it may reduce rendering performance on some systems.
521+
522+
Another thing that distincts windowed rendering from off-screen
523+
rendering is that when creating browser you have to call SetAsOffscreen
524+
method on the WindowInfo object. Code from the example:
525+
526+
```Python
527+
parent_window_handle = 0
528+
window_info = cef.WindowInfo()
529+
window_info.SetAsOffscreen(parent_window_handle)
530+
browser = cef.CreateBrowserSync(window_info=window_info,
531+
url=URL)
532+
```
533+
534+
Also after creating browser it is required to let CEF know that
535+
viewport size is available and that OnPaint callback may be called
536+
(this callback will be explained in a moment) by calling
537+
WasResized method:
538+
539+
```Python
540+
browser.WasResized()
541+
```
542+
543+
Off-screen rendering requires implementing [RenderHandler](../api/RenderHandler.md#renderhandler-interface)
544+
which is one of client handlers and how to use them was
545+
explained earlier in the tutorial in the [Client handlers](#client-handlers)
546+
section. For basic off-screen rendering it is enough to
547+
implement only two methods: [GetViewRect](../api/RenderHandler.md#getviewrect)
548+
and [OnPaint](../api/RenderHandler.md#onpaint). In the GetViewRect
549+
callback information on viewport size will be provided to CEF:
550+
551+
```Python
552+
def GetViewRect(self, rect_out, **_):
553+
rect_out.extend([0, 0, VIEWPORT_SIZE[0], VIEWPORT_SIZE[1]])
554+
return True
555+
```
556+
557+
In this callback viewport size is returned via |rect_out| which
558+
is of type "list" and thus is passed by reference. Additionally
559+
a True value is returned by function to notify CEF that rectangle
560+
was provided.
561+
562+
In the OnPaint callback CEF provides a [PaintBufer](../api/PaintBuffer.md#paintbuffer-object) object, which is a pixel buffer of the
563+
browser view. This object has [GetIntPointer](../api/PaintBuffer.md#getintpointer)
564+
and [GetString](../api/PaintBuffer.md#getstring) methods. In the
565+
example the latter method is used which returns bytes. The method
566+
name is a bit confusing for Python 3 users, but in Python 2 bytes
567+
were strings and thus the name. Here is the code:
568+
569+
```Python
570+
def OnPaint(self, browser, element_type, paint_buffer, **_):
571+
if element_type == cef.PET_VIEW:
572+
buffer_string = paint_buffer.GetString(mode="rgba",
573+
origin="top-left")
574+
browser.SetUserData("OnPaint.buffer_string", buffer_string)
575+
```
576+
577+
The |element_type| argument can be either of cef.PET_VIEW
578+
(main view) or cef.PET_POPUP (for drawing popup widgets like
579+
`<select>` element). You can see a call to [SetUserData](../api/Browser.md#setuserdata)
580+
which is a helper method for storing custom data associated with
581+
browser. This data is stored for later use when page completes
582+
loading. During loading of a page there are many calls to OnPaint
583+
callback and it is not yet known which call is the last when
584+
loading completes and thus image buffer is stored for later use.
585+
586+
The screenshot example also implements another handler named
587+
[LoadHanadler](../api/LoadHandler.md#loadhandler-interface)
588+
and two of its callbacks: [OnLoadingStateChange](../api/LoadHandler.md#onloadingstatechange)
589+
and [OnLoadError](../api/LoadHandler.md#onloaderror). The
590+
OnLoadingStateChange callbacks notifies when web page loading
591+
completes and OnLoadError callback notifies if loading of
592+
page failed. When loading succeeds a function save_screenshot()
593+
is called which retrieves image buffer that was earlier stored
594+
in the browser object and then uses PIL image library to save
595+
it as a PNG image.
596+
597+
At the end, it is worth noting that there is yet an another
598+
option for off-screen rendering named [windowless_frame_rate](../api/BrowserSettings.md#windowless_frame_rate)
599+
(can be passed to [CreateBrowserSync](../api/cefpython.md#createbrowsersync)),
600+
but is not used by this example. It sets the maximum rate
601+
in frames per second (fps) that OnPaint callback will be called
602+
603+
Currently CEF requires a window manager on Linux even in off-screen
604+
rendering mode. On systems without screen or any input you can use
605+
something like [Xvfb](https://en.wikipedia.org/wiki/Xvfb) which
606+
performs all graphical operations in memory without showing any
607+
screen output. Pure headless mode is currently not supported in
608+
CEF, but that may change in the future. CEF currently depends on
609+
X11 window manager, but there are plans to support alternative
610+
window managers by adding Ozone support - [upstream issue #1989](https://bitbucket.org/chromiumembedded/cef/issues/1989/linux-add-ozone-support-as-an-alternative).
611+
612+
The screenshot.py example is just a basic showcase of off-screen
613+
rendering features. That screenshot feature should also be possible
614+
to implement using windowed rendering using a normal GUI window,
615+
but a hidden one with height set to some very big value that it
616+
wouldn't fit on screen. You could then render contents of this
617+
window to an image. For example in Qt you can do this by using
618+
QImage/QPainter classes along with a call to QWidget.render().
619+
473620
## Build executable
474621

475622
There are no official examples for building executable using

examples/Examples-README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,22 @@ python hello_world.py
2121
## Supported examples
2222

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

26-
- [hello_world.py](hello_world.py): basic example, doesn't require any
28+
**Featured**
29+
30+
- [hello_world.py](hello_world.py) - Basic example, doesn't require any
2731
third party GUI framework to run
28-
- [tutorial.py](tutorial.py): example from [Tutorial](../docs/Tutorial.md)
32+
- [tutorial.py](tutorial.py) - Example from [Tutorial](../docs/Tutorial.md)
33+
- [screenshot.py](screenshot.py) - Example of off-screen rendering mode
34+
to create a screenshot of a web page. Also referenced in Tutorial
35+
in the [Off-screen rendering](../docs/Tutorial.md#off-screen-rendering)
36+
section.
37+
38+
**Embedding using various GUI frameworks**
39+
2940
- [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/)
3041
library (GTK 2)
3142
- [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject)
@@ -38,9 +49,6 @@ maintained:
3849
- [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/)
3950
toolkit
4051

41-
If there are any issues in examples read top comments in sources
42-
to see whether this is a known issue with available workarounds.
43-
4452
**Unit tests**
4553

4654
There are also available unit tests and its usage of the API can

0 commit comments

Comments
 (0)