完成区域截图 高亮功能测试

master
yutent 2023-08-30 19:22:53 +08:00
parent 2ba819474b
commit 59e6e16a94
3 changed files with 319 additions and 12 deletions

View File

@ -6,6 +6,9 @@ import gi, cairo
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf from gi.repository import Gtk, Gdk, GdkPixbuf
from utils import blur_image
# 获取默认显示器 # 获取默认显示器
dp = Gdk.Display.get_default() dp = Gdk.Display.get_default()
@ -43,6 +46,8 @@ def on_mouse_move(widget, event):
class App(Gtk.Window): class App(Gtk.Window):
saved = False
def __init__(self): def __init__(self):
Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
@ -54,26 +59,25 @@ class App(Gtk.Window):
# 设置透明背景 # 设置透明背景
screen = self.get_screen() screen = self.get_screen()
visual = screen.get_rgba_visual() visual = screen.get_rgba_visual()
# if visual and screen.is_composited(): if visual and screen.is_composited():
# self.set_visual(visual) self.set_visual(visual)
# self.set_app_paintable(True) self.set_app_paintable(True)
self.set_focus()
self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) # self.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
# window.connect("button-press-event", on_mouse_move) # window.connect("button-press-event", on_mouse_move)
# window.connect("button-release-event", on_mouse_move) # window.connect("button-release-event", on_mouse_move)
# window.connect("motion-notify-event", on_mouse_move) # window.connect("motion-notify-event", on_mouse_move)
# window.connect("motion-notify-event", on_mouse_move) # window.connect("motion-notify-event", on_mouse_move)
# layout = Gtk.Fixed() layout = Gtk.Fixed()
# btn = Gtk.Button(label = 'hello world')
# btn.set_size_request(200, 32)
# layout.put(btn, 0, 0)
# self.add(layout) # self.add(layout)
layout = Gtk.Fixed() # layout = Gtk.Fixed()
self.drawing_area = Gtk.DrawingArea() self.drawing_area = Gtk.DrawingArea()
self.drawing_area.set_size_request(total_width, total_height) self.drawing_area.set_size_request(total_width, total_height)
@ -85,7 +89,11 @@ class App(Gtk.Window):
self.drawing_area.connect("motion-notify-event", self.on_motion_notify) self.drawing_area.connect("motion-notify-event", self.on_motion_notify)
layout.add(self.drawing_area) layout.add(self.drawing_area)
btn = Gtk.Button(label = 'hello world')
btn.set_size_request(200, 60)
layout.put(btn, 200, 100)
self.layout = layout
self.add(layout) self.add(layout)
# Initialize rectangle and control point size # Initialize rectangle and control point size
self.start_x = None self.start_x = None
@ -96,7 +104,26 @@ class App(Gtk.Window):
self.dragging = False self.dragging = False
self.resize_dragging = False self.resize_dragging = False
self.connect("draw", on_draw) self.connect("draw", self.on_before_start)
self.connect("delete-event", Gtk.main_quit)
self.connect("key-press-event", self.on_key_down)
def on_before_start(self, win, cr):
if not self.saved:
# print('又进来了')
window = Gdk.get_default_root_window()
width, height = window.get_width(), window.get_height()
self.entire_desktop = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)
# self.entire_desktop.savev("entire_desktop.png", "png", [], [])
self.saved = True
cr.set_source_rgba(0, 0, 0, 0.3) # 设置颜色和透明度
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
@ -104,12 +131,27 @@ class App(Gtk.Window):
if self.start_x is not None and self.start_y is not None: if self.start_x is not None and self.start_y is not None:
width = self.current_x - self.start_x width = self.current_x - self.start_x
height = self.current_y - self.start_y height = self.current_y - self.start_y
# print(abs(width), abs(height))
# 白色
# cr.set_source_rgb(1, 1, 1)
cr.set_source_rgb(208/255, 234/255, 1)
cr.set_line_width(2)
cr.set_dash([6.0, 4.0]) cr.set_dash([6.0, 4.0])
cr.rectangle(self.start_x, self.start_y, width, height) cr.rectangle(self.start_x, self.start_y, width, height)
cr.stroke() cr.stroke()
center = (self.start_x + width / 2, self.start_y + height / 2)
# draw resizing dots # draw resizing dots
for x in [self.start_x, self.start_x + width / 2, self.current_x]: for x in [self.start_x, center[0], self.current_x]:
for y in [self.start_y, self.start_y + height / 2, self.current_y]: for y in [self.start_y, center[1], self.current_y]:
# 中心的圆点不画
if x == center[0] and y == center[1]:
continue
cr.arc(x, y, 5, 0, 2 * 3.14) cr.arc(x, y, 5, 0, 2 * 3.14)
cr.fill() cr.fill()
@ -126,6 +168,7 @@ class App(Gtk.Window):
def on_button_release(self, widget, event): def on_button_release(self, widget, event):
if event.button == 1: # left mouse button if event.button == 1: # left mouse button
self.dragging = False self.dragging = False
self.screenshot()
self.queue_draw() self.queue_draw()
@ -136,6 +179,26 @@ class App(Gtk.Window):
self.queue_draw() self.queue_draw()
def screenshot(self):
x = self.start_x
y = self.start_y
w = self.current_x - x
h = self.current_y - y
pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, abs(w), abs(h))
self.entire_desktop.copy_area(x, y, w, h, pixbuf, 0, 0)
# pixbuf = blur_image(pixbuf)
# pixbuf.savev("screenshot.png", "png", [], [])
img = Gtk.Image()
img.set_from_pixbuf(pixbuf)
self.layout.put(img, x + 1, y + 1)
self.layout.show_all()
def on_key_down(self, win, ev):
if ev.keyval == Gdk.KEY_Escape:
Gtk.main_quit()
def run(self): def run(self):
try: try:

90
draft/utils.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
import gi, io, base64, threading
from gi.repository import Gdk, GLib, GdkPixbuf, Gio, GObject
from PIL import Image, ImageFilter
empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
def pixbuf_to_pil(pixbuf):
data = pixbuf.get_pixels()
w = pixbuf.get_width()
h = pixbuf.get_height()
stride = pixbuf.get_rowstride()
mode = "RGBA" if pixbuf.get_has_alpha() else "RGB"
img = Image.frombytes(mode, (w, h), data, "raw", mode, stride)
return img
def pil_to_pixbuf(img):
data = []
for p in img.getdata():
data.extend(p)
data = bytes(data)
alpha = img.mode == 'RGBA'
w, h = img.size
rowstride = w * 4
try:
return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride)
except:
return empty_pixbuf
def pic_to_pixbuf(pic):
data = pic.data
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
try:
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
except:
return empty_pixbuf
def base64_to_pixbuf(base64_str):
data = base64.b64decode(base64_str)
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
try:
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
except:
return empty_pixbuf
def blur_image(pixbuf):
# 加载图片并确认该图片为RGBA模式保证透明度
img = pixbuf_to_pil(pixbuf).convert('RGBA')
mask = Image.new('RGBA', img.size, (64, 64, 64, 0))
img = img.filter(ImageFilter.GaussianBlur(radius = 6))
img.alpha_composite(mask)
return pil_to_pixbuf(img)
# 定义一个异步修饰器, 用于在子线程中运行一些会阻塞主线程的任务
def run_async(func):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread
return wrapper
# 类型js的settimeout的修饰器
def set_timeout(timeout = 0.5):
def decorator(callback):
def wrapper(*args):
t = threading.Timer(timeout, callback, args=args)
t.start()
return t
return wrapper
return decorator
# 定义一个修饰器, 用于将当前方法转到主线程中运行 (子线程中调用方法时)
def idle(func):
def wrapper(*args):
GObject.idle_add(func, *args)
return wrapper

154
usr/lib/xshot/shot_the_window.py Executable file
View File

@ -0,0 +1,154 @@
#!/usr/bin/env python3
import gi, sys, os, cairo, math
import dbus
import dbus.service, dbus.mainloop.glib
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
from PIL import Image, ImageFilter, ImageChops
app_id = 'fun.wkit.xshot'
def pixbuf_to_pil(pixbuf):
data = pixbuf.get_pixels()
w = pixbuf.get_width()
h = pixbuf.get_height()
stride = pixbuf.get_rowstride()
mode = "RGBA" if pixbuf.get_has_alpha() else "RGB"
img = Image.frombytes(mode, (w, h), data, "raw", mode, stride)
return img
def pil_to_pixbuf(img):
data = []
for p in img.getdata():
data.extend(p)
data = bytes(data)
alpha = img.mode == 'RGBA'
w, h = img.size
rowstride = w * 4
return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride)
def set_radius(pixbuf, radius = 4):
w = pixbuf.get_width()
h = pixbuf.get_height()
surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
ctx = cairo.Context(surface)
Gdk.cairo_set_source_pixbuf(ctx, pixbuf, 0, 0)
# 左上角 圆角
ctx.arc(radius, radius, radius, -math.pi, -math.pi / 2)
ctx.line_to(w - radius, 0)
# 右上角 圆角
ctx.arc(w - radius, radius, radius, -math.pi / 2, 0)
ctx.line_to(w, -radius)
# 右下角 圆角
ctx.arc(w - radius, h - radius, radius, 0, math.pi / 2)
ctx.line_to(radius, h)
# 左下角 圆角
ctx.arc(radius, h - radius, radius, math.pi / 2, math.pi)
ctx.close_path()
ctx.clip()
ctx.paint()
return Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)
def add_shadow(img):
# 加载图片并确认该图片为RGBA模式保证透明度
img = set_radius(img)
img = pixbuf_to_pil(img).convert('RGBA')
w,h = img.size
w += 64
h += 64
shadow = Image.new('RGBA', img.size, (80,80,80,223))
shadow = pixbuf_to_pil(set_radius(pil_to_pixbuf(shadow)))
#new_img = Image.new('RGBA', (w,h), (0,0,0,0))
new_img = Image.new('RGBA', (w,h), (255,255,255,255))
new_img.paste(shadow, (32, 32), shadow)
new_img = new_img.filter(ImageFilter.GaussianBlur(radius = 12))
new_img.paste(img, (32, 32), img)
return pil_to_pixbuf(new_img)
class Application(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self, application_id = app_id)
display = Gdk.Display.get_default()
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
def do_activate(self):
self.shot_the_window()
def copy(self, image):
# 写图片进剪切板
self.clipboard.clear()
self.clipboard.set_image(image)
self.clipboard.store()
def shot_the_window(self):
screen = Gdk.Screen.get_default()
win = screen.get_active_window()
width = win.get_width()
height = win.get_height()
# 获取窗口图像
image = Gdk.pixbuf_get_from_window(win, 0, 0, width, height)
image = add_shadow(image)
self.copy(image)
class ApplicationService(dbus.service.Object):
def __init__(self, app):
self.app = app
bus_name = dbus.service.BusName(app_id, bus = dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, '/')
@dbus.service.method(app_id)
def call_app(self):
self.app.shot_the_window()
if __name__ == "__main__":
dbus.mainloop.glib.DBusGMainLoop(set_as_default = True)
bus = dbus.SessionBus()
try:
obj = bus.get_object(app_id, '/')
obj.call_app()
sys.exit(0)
except dbus.DBusException:
pass
app = Application()
app.run(sys.argv)
ApplicationService(app)
Gtk.main()