X Tutup
Skip to content

Commit e3226db

Browse files
committed
Implement Browser.GetImage on Linux (cztomczak#427)
1 parent c599c59 commit e3226db

File tree

12 files changed

+163
-42
lines changed

12 files changed

+163
-42
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ Additional information for v31.2 release:
374374
* [GetFrameCount](api/Browser.md#getframecount)
375375
* [GetFrameIdentifiers](api/Browser.md#getframeidentifiers)
376376
* [GetFrameNames](api/Browser.md#getframenames)
377+
* [GetImage](api/Browser.md#getimage)
377378
* [GetJavascriptBindings](api/Browser.md#getjavascriptbindings)
378379
* [GetMainFrame](api/Browser.md#getmainframe)
379380
* [GetNSTextInputContext](api/Browser.md#getnstextinputcontext)

api/API-index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
* [GetFrameCount](Browser.md#getframecount)
6161
* [GetFrameIdentifiers](Browser.md#getframeidentifiers)
6262
* [GetFrameNames](Browser.md#getframenames)
63+
* [GetImage](Browser.md#getimage)
6364
* [GetJavascriptBindings](Browser.md#getjavascriptbindings)
6465
* [GetMainFrame](Browser.md#getmainframe)
6566
* [GetNSTextInputContext](Browser.md#getnstextinputcontext)

api/Browser.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Table of contents:
3939
* [GetFrameCount](#getframecount)
4040
* [GetFrameIdentifiers](#getframeidentifiers)
4141
* [GetFrameNames](#getframenames)
42+
* [GetImage](#getimage)
4243
* [GetJavascriptBindings](#getjavascriptbindings)
4344
* [GetMainFrame](#getmainframe)
4445
* [GetNSTextInputContext](#getnstextinputcontext)
@@ -420,6 +421,29 @@ Returns the identifiers of all existing frames.
420421
Returns the names of all existing frames. This list does not include the main frame.
421422

422423

424+
### GetImage
425+
426+
| | |
427+
| --- | --- |
428+
| __Return__ | tuple(bytes buffer, int width, int height) |
429+
430+
Currently works only on Linux (Issue [#427](../../../issues/427)).
431+
432+
Get browser contents as image. Only screen visible contents are returned.
433+
434+
Returns an RGB buffer which can be converted to an image
435+
using PIL library with such code:
436+
437+
```py
438+
from PIL import Image, ImageFile
439+
buffer_len = (width * 3 + 3) & -4
440+
image = Image.frombytes("RGB", (width, height), data,
441+
"raw", "RGB", buffer_len, 1)
442+
ImageFile.MAXBLOCK = width * height
443+
image.save("image.png", "PNG")
444+
```
445+
446+
423447
### GetJavascriptBindings
424448

425449
| | |

src/browser.pyx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,35 @@ cdef class PyBrowser:
274274
cpdef JavascriptBindings GetJavascriptBindings(self):
275275
return self.javascriptBindings
276276

277+
cdef bytes b(self, int x):
278+
return struct.pack(b'<B', x)
279+
280+
cpdef object GetImage(self):
281+
IF UNAME_SYSNAME == "Linux":
282+
cdef XImage* image
283+
image = x11.CefBrowser_GetImage(self.cefBrowser)
284+
if not image:
285+
return None
286+
cdef int width = image.width
287+
cdef int height = image.height
288+
cdef list pixels = [b'0'] * (3 * width * height)
289+
cdef int x, y, blue, green, red, offset
290+
cdef unsigned long pixel
291+
for x in range(width):
292+
for y in range(height):
293+
pixel = XGetPixel(image, x, y)
294+
blue = pixel & 255
295+
green = (pixel & 65280) >> 8
296+
red = (pixel & 16711680) >> 16
297+
offset = (x + width * y) * 3
298+
pixels[offset:offset+3] = self.b(red), self.b(green),\
299+
self.b(blue)
300+
XDestroyImage(image)
301+
return b''.join(pixels), width, height
302+
ELSE:
303+
NonCriticalError("GetImage not implemented on this platform")
304+
return None
305+
277306
# --------------
278307
# CEF API.
279308
# --------------

src/cef_v59..v66_changes.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ internal/cef_types.h
6060
- + Remove: UR_FLAG_ALLOW_CACHED_CREDENTIALS
6161

6262

63+
TODO
64+
----
65+
1. Compare src/handler/dialog_handler_gtk.cpp (and .h) with upstream
66+
cefclient files
67+
2. In subprocess/print_handler_gtk.cpp use GetWindow implementation
68+
from x11.cpp
69+
6370
NEW FEATURES
6471
------------
6572

src/cefpython.pyx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ import json
132132
import datetime
133133
# noinspection PyUnresolvedReferences
134134
import random
135+
# noinspection PyUnresolvedReferences
136+
import struct
135137

136138
if sys.version_info.major == 2:
137139
# noinspection PyUnresolvedReferences

src/client_handler/dialog_handler_gtk.cpp

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -138,49 +138,10 @@ void AddFilters(GtkFileChooser* chooser,
138138
}
139139
}
140140

141-
GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
142-
// -- REWRITTEN FOR CEF PYTHON USE CASE --
143-
// X11 window handle
144-
::Window xwindow = browser->GetHost()->GetWindowHandle();
145-
// X11 display
146-
::Display* xdisplay = cef_get_xdisplay();
147-
// GDK display
148-
GdkDisplay* gdk_display = NULL;
149-
if (xdisplay) {
150-
// See if we can find GDK display using X11 display
151-
gdk_display = gdk_x11_lookup_xdisplay(xdisplay);
152-
}
153-
if (!gdk_display) {
154-
// If not then get the default display
155-
gdk_display = gdk_display_get_default();
156-
}
157-
if (!gdk_display) {
158-
// The tkinter_.py and hello_world.py examples do not use GTK
159-
// internally, so GTK wasn't yet initialized and must do it
160-
// now, so that display is available. Also must install X11
161-
// error handlers to avoid 'BadWindow' errors.
162-
LOG(INFO) << "[Browser process] Initialize GTK";
163-
gtk_init(0, NULL);
164-
InstallX11ErrorHandlers();
165-
// Now the display is available
166-
gdk_display = gdk_display_get_default();
167-
}
168-
// In kivy_.py example getting error message:
169-
// > Can't create GtkPlug as child of non-GtkSocket
170-
// However dialog handler works just fine.
171-
GtkWidget* widget = gtk_plug_new_for_display(gdk_display, xwindow);
172-
// Getting top level widget doesn't seem to be required.
173-
// OFF: GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
174-
GtkWindow* window = GTK_WINDOW(widget);
175-
if (!window) {
176-
LOG(ERROR) << "No GtkWindow for browser";
177-
}
178-
return window;
179-
}
180-
181141
} // namespace
182142

183143

144+
184145
ClientDialogHandlerGtk::ClientDialogHandlerGtk()
185146
: gtk_dialog_(NULL) {
186147
}
@@ -238,7 +199,7 @@ bool ClientDialogHandlerGtk::OnFileDialog(
238199
}
239200
}
240201

241-
GtkWindow* window = GetWindow(browser);
202+
GtkWindow* window = CefBrowser_GetGtkWindow(browser);
242203
if (!window)
243204
return false;
244205

@@ -378,7 +339,7 @@ bool ClientDialogHandlerGtk::OnJSDialog(
378339
// title += CefFormatUrlForSecurityDisplay(origin_url).ToString();
379340
}
380341

381-
GtkWindow* window = GetWindow(browser);
342+
GtkWindow* window = CefBrowser_GetGtkWindow(browser);
382343
if (!window)
383344
return false;
384345

src/client_handler/x11.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,65 @@ void SetX11WindowTitle(CefRefPtr<CefBrowser> browser, char* title) {
4646
::Display* xdisplay = cef_get_xdisplay();
4747
XStoreName(xdisplay, xwindow, title);
4848
}
49+
50+
GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr<CefBrowser> browser) {
51+
// -- REWRITTEN FOR CEF PYTHON USE CASE --
52+
// X11 window handle
53+
::Window xwindow = browser->GetHost()->GetWindowHandle();
54+
// X11 display
55+
::Display* xdisplay = cef_get_xdisplay();
56+
// GDK display
57+
GdkDisplay* gdk_display = NULL;
58+
if (xdisplay) {
59+
// See if we can find GDK display using X11 display
60+
gdk_display = gdk_x11_lookup_xdisplay(xdisplay);
61+
}
62+
if (!gdk_display) {
63+
// If not then get the default display
64+
gdk_display = gdk_display_get_default();
65+
}
66+
if (!gdk_display) {
67+
// The tkinter_.py and hello_world.py examples do not use GTK
68+
// internally, so GTK wasn't yet initialized and must do it
69+
// now, so that display is available. Also must install X11
70+
// error handlers to avoid 'BadWindow' errors.
71+
LOG(INFO) << "[Browser process] Initialize GTK";
72+
gtk_init(0, NULL);
73+
InstallX11ErrorHandlers();
74+
// Now the display is available
75+
gdk_display = gdk_display_get_default();
76+
}
77+
// In kivy_.py example getting error message:
78+
// > Can't create GtkPlug as child of non-GtkSocket
79+
// However dialog handler works just fine.
80+
GtkWidget* widget = gtk_plug_new_for_display(gdk_display, xwindow);
81+
// Getting top level widget doesn't seem to be required.
82+
// OFF: GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
83+
GtkWindow* window = GTK_WINDOW(widget);
84+
if (!window) {
85+
LOG(ERROR) << "No GtkWindow for browser";
86+
}
87+
return window;
88+
}
89+
90+
XImage* CefBrowser_GetImage(CefRefPtr<CefBrowser> browser) {
91+
::Display* display = cef_get_xdisplay();
92+
if (!display) {
93+
LOG(ERROR) << "XOpenDisplay failed in CefBrowser_GetImage";
94+
return NULL;
95+
}
96+
::Window browser_window = browser->GetHost()->GetWindowHandle();
97+
XWindowAttributes attrs;
98+
if (!XGetWindowAttributes(display, browser_window, &attrs)) {
99+
LOG(ERROR) << "XGetWindowAttributes failed in CefBrowser_GetImage";
100+
return NULL;
101+
}
102+
XImage* image = XGetImage(display, browser_window,
103+
0, 0, attrs.width, attrs.height,
104+
AllPlanes, ZPixmap);
105+
if (!image) {
106+
LOG(ERROR) << "XGetImage failed in CefBrowser_GetImage";
107+
return NULL;
108+
}
109+
return image;
110+
}

src/client_handler/x11.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
#pragma once
66

77
#include <X11/Xlib.h>
8+
#include <gtk/gtk.h>
9+
#include <gdk/gdkx.h>
10+
811
#include "include/cef_browser.h"
912

1013
void InstallX11ErrorHandlers();
1114
void SetX11WindowBounds(CefRefPtr<CefBrowser> browser,
1215
int x, int y, int width, int height);
1316
void SetX11WindowTitle(CefRefPtr<CefBrowser> browser, char* title);
17+
18+
GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr<CefBrowser> browser);
19+
XImage* CefBrowser_GetImage(CefRefPtr<CefBrowser> browser);

src/extern/linux.pxd

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,26 @@ cdef extern from "gtk/gtk.h" nogil:
77
ctypedef void* GtkWidget
88
cdef GtkWidget* gtk_plug_new(GdkNativeWindow socket_id)
99
cdef void gtk_widget_show(GtkWidget* widget)
10+
11+
ctypedef char* XPointer
12+
ctypedef struct XImage:
13+
int width
14+
int height
15+
int xoffset # number of pixels offset in X direction
16+
int format # XYBitmap, XYPixmap, ZPixmap
17+
char *data # pointer to image data
18+
int byte_order # data byte order, LSBFirst, MSBFirst
19+
int bitmap_unit # quant. of scanline 8, 16, 32
20+
int bitmap_bit_order # LSBFirst, MSBFirst
21+
int bitmap_pad # 8, 16, 32 either XY or ZPixmap
22+
int depth # depth of image
23+
int bytes_per_line # accelerator to next scanline
24+
int bits_per_pixel # bits per pixel (ZPixmap)
25+
unsigned long red_mask # bits in z arrangement
26+
unsigned long green_mask
27+
unsigned long blue_mask
28+
XPointer *obdata
29+
void *funcs
30+
void XDestroyImage(XImage *ximage)
31+
unsigned long XGetPixel(XImage* image, int x, int y)
32+

0 commit comments

Comments
 (0)
X Tutup