【问题标题】:Send canvas data between applications在应用程序之间发送画布数据
【发布时间】:2015-12-05 11:16:54
【问题描述】:

main.py

from kivy.base import EventLoop
from kivy.config import Config
from kivy.graphics import Color, Line
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.clock import Clock
CURSOR = (
    '       @@@@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @@@@             ',
    '                        ',
    '@@@@@@ @@@@ @@@@@@      ',
    '@----@ @--@ @----@      ',
    '@----@ @--@ @----@      ',
    '@@@@@@ @@@@ @@@@@@      ',
    '                        ',
    '       @@@@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @@@@             ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
)
class Update_Location(Widget):
    pass

class CanvasWidget(Widget):
    line_width = 2

    def on_touch_down(self, touch):
        if Widget.on_touch_down(self, touch):
           return

        with self.canvas:
            touch.ud['current_line'] = Line(
                points=(touch.x, touch.y),
                width=self.line_width)

    def on_touch_move(self, touch):
        if 'current_line' in touch.ud:
            touch.ud['current_line'].points += (touch.x, touch.y)

    def set_color(self, new_color):
        self.last_color = new_color
        self.canvas.add(Color(*new_color))

    def set_line_width(self, line_width='Normal'):
        self.line_width = {
            'Thin': 1, 'Normal': 2, 'Thick': 4
        }[line_width]

    def clear_canvas(self):
        saved = self.children[:]
        self.clear_widgets()
        self.canvas.clear()
        for widget in saved:
            self.add_widget(widget)
        self.set_color(self.last_color)
    def start_server(self):
        host = '127.0.0.1'
        port = 5000
        notification_text="Server started on host: "+host+" and port: "+str(port)
        server_start=Popup(title='Notification',content=Label(text=notification_text),size_hint=(.75,.75),auto_dismiss=True)
        server_start.open()
        Clock.schedule_interval(server_start.dismiss, 3)


class PaintApp(App):
    def build(self):
        EventLoop.ensure_window()
        if EventLoop.window.__class__.__name__.endswith('Pygame'):
            try:
                from pygame import mouse

                a, b = pygame_compile_cursor()
                mouse.set_cursor((24, 24), (9, 9), a, b)
            except:
                pass
        #boxlayout
        self.layout = BoxLayout(orientation='vertical')

        self.canvas_widget = CanvasWidget()
        self.canvas_widget.set_color(
            get_color_from_hex('#2980b9'))
        self.layout.add_widget(self.canvas_widget)
        #self.layout.add_widget(Label(text="Started Server : False , Connected to Server : False",color=(1,1,1),size_hint=(1, .1)))
        return self.layout

        #return self.canvas_widget


class RadioButton(ToggleButton):
    def _do_press(self):
        if self.state == 'normal':
            ToggleButtonBehavior._do_press(self)


def pygame_compile_cursor(black='@', white='-'):
    aa, bb = [], []
    a = b = 0
    i = 8
    for s in CURSOR:
        for c in s:
            a <<= 1
            b <<= 1
            i -= 1
            if c == black:
                a |= 1
                b |= 1
            elif c == white:
                b |= 1

            if not i:
                aa.append(a)
                bb.append(b)
                a = b = 0
                i = 8

    return tuple(aa), tuple(bb)

if __name__ == '__main__':
    Config.set('graphics', 'width', '960')
    Config.set('graphics', 'height', '540')  # 16:9
    # Config.set('graphics', 'resizable', '0')
    # Config.set('input', 'mouse', 'mouse,disable_multitouch')

    from kivy.core.window import Window
    Window.clearcolor = get_color_from_hex('#ffffff')

    PaintApp().run()

server.py

import socket

def start_server(host,port):
    s = socket.socket()
    s.bind((host,port))

    s.listen(1)
    c, addr = s.accept()
    print "Connection from: " + str(addr)
    while True:
        data = c.recv(1024)
        if not data:
            break
        print "from connected user: " + str(data)
        data = str(data).upper()
        print "sending: " + str(data)
        c.send(data)
    c.close()

if __name__ == '__main__':
    start_server()

以上是我的简单绘画应用程序的代码(来自 kivy blue prints)。

我的目标是在本地主机上的一个应用程序上启动服务器,然后其他应用程序连接到它。那些可以连接到它的人应该从他们连接的服务器上更新他们的画布。如何发送数据和更新画布。我使用 python 的套接字库在上面的代码中创建了一个服务器。

我不想使用 http 请求/线程来建立与套接字的连接并使用 async.io。

【问题讨论】:

  • 让服务器发出(可能是广播)JSON 化的画布命令对象。每个命令都可以是上下文命令的索引以及参数数组:{i:0,p:[]} 其中i 是命令数组var cmds=['beginPath',...] 的索引,p 是任何命令参数的数组。然后就可以使用context.apply在客户端执行命令了。

标签: python sockets canvas kivy


【解决方案1】:

我使用支持发布的 /canvas 端点创建了一个快速 REST 烧瓶服务。我还创建了 CanvasWidget 类的 lines 成员,以保存如何重新创建数据以将其发送到服务器。我曾考虑过逐个像素地进行,但我认为跟踪线条创建更优雅,并且如果需要,将支持撤消重做功能。

方向

  • 启动 server.py
  • 启动 main.py
  • 画一些东西
  • 按“s”键将当前画布发送到服务器
  • 退出 main.py 应用程序
  • 启动 main.py 应用程序
  • 按“r”键接收服务器当前状态

main.py

from kivy.app import App
from kivy.base import EventLoop
from kivy.config import Config
from kivy.graphics import Color, Line
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.clock import Clock
from kivy.network.urlrequest import UrlRequest
import flask

CURSOR = (
    '       @@@@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @@@@             ',
    '                        ',
    '@@@@@@ @@@@ @@@@@@      ',
    '@----@ @--@ @----@      ',
    '@----@ @--@ @----@      ',
    '@@@@@@ @@@@ @@@@@@      ',
    '                        ',
    '       @@@@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @--@             ',
    '       @@@@             ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
    '                        ',
)
class Update_Location(Widget):
    pass

class CanvasWidget(Widget):
    line_width = 2
    lines = []

    def __init__(self, **kwargs):
        super(CanvasWidget, self).__init__(**kwargs)
        self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
        self._keyboard.bind(on_key_down=self._on_keyboard_down)

    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        self._keyboard = None

    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 's':
            print 'send'
            json = flask.json.dumps(self.lines)
            req = UrlRequest('http://127.0.0.1:5000/canvas', req_body=json)
        elif keycode[1] == 'r':
            print 'receive'
            req = UrlRequest('http://127.0.0.1:5000/canvas', self.load_state)

    def on_touch_down(self, touch):
        if Widget.on_touch_down(self, touch):
           return

        with self.canvas:
            touch.ud['current_line'] = Line(
                points=(touch.x, touch.y),
                width=self.line_width)
            self.lines.append({'points':[touch.x,touch.y], 'width':self.line_width})

    def on_touch_move(self, touch):
        if 'current_line' in touch.ud:
            touch.ud['current_line'].points += (touch.x, touch.y)
            self.lines[-1]['points'].append(touch.x)
            self.lines[-1]['points'].append(touch.y)

    def set_color(self, new_color):
        self.last_color = new_color
        self.canvas.add(Color(*new_color))

    def set_line_width(self, line_width='Normal'):
        self.line_width = {
            'Thin': 1, 'Normal': 2, 'Thick': 4
        }[line_width]

    def clear_canvas(self):
        saved = self.children[:]
        self.clear_widgets()
        self.canvas.clear()
        for widget in saved:
            self.add_widget(widget)
        self.set_color(self.last_color)

    def load_state(self, req, results):
        print 'load_state req', repr(req)
        print 'load_state results', repr(results)
        #needs some validation here
        self.clear_canvas()
        with self.canvas:
            for line in results:
                Line(points=line['points'], width=line['width'])
        self.line = results 

    def start_server(self):
        host = '127.0.0.1'
        port = 5000
        notification_text="Server started on host: "+host+" and port: "+str(port)
        server_start=Popup(title='Notification',content=Label(text=notification_text),size_hint=(.75,.75),auto_dismiss=True)
        server_start.open()
        Clock.schedule_interval(server_start.dismiss, 3)


class PaintApp(App):
    def build(self):
        EventLoop.ensure_window()
        if EventLoop.window.__class__.__name__.endswith('Pygame'):
            try:
                from pygame import mouse

                a, b = pygame_compile_cursor()
                mouse.set_cursor((24, 24), (9, 9), a, b)
            except:
                pass
        #boxlayout
        self.layout = BoxLayout(orientation='vertical')

        self.canvas_widget = CanvasWidget()
        self.canvas_widget.set_color(
            get_color_from_hex('#2980b9'))
        self.layout.add_widget(self.canvas_widget)
        #self.layout.add_widget(Label(text="Started Server : False , Connected to Server : False",color=(1,1,1),size_hint=(1, .1)))
        return self.layout
        #return self.canvas_widget


class RadioButton(ToggleButton):
    def _do_press(self):
        if self.state == 'normal':
            ToggleButtonBehavior._do_press(self)


def pygame_compile_cursor(black='@', white='-'):
    aa, bb = [], []
    a = b = 0
    i = 8
    for s in CURSOR:
        for c in s:
            a <<= 1
            b <<= 1
            i -= 1
            if c == black:
                a |= 1
                b |= 1
            elif c == white:
                b |= 1

            if not i:
                aa.append(a)
                bb.append(b)
                a = b = 0
                i = 8

    return tuple(aa), tuple(bb)

if __name__ == '__main__':
    Config.set('graphics', 'width', '960')
    Config.set('graphics', 'height', '540')  # 16:9
    # Config.set('graphics', 'resizable', '0')
    # Config.set('input', 'mouse', 'mouse,disable_multitouch')

    from kivy.core.window import Window
    Window.clearcolor = get_color_from_hex('#ffffff')

    PaintApp().run()

server.py

from flask import Flask, jsonify, request, json
app = Flask(__name__)

data = []

@app.route('/canvas', methods = ['GET', 'POST'])
def canvas():
    global data
    if request.method == 'GET':
        tmp = jsonify({})
        tmp.set_data(json.dumps(data))
        return tmp

    if request.method == 'POST':
        data = json.loads(request.data)
        tmp = jsonify({})
        tmp.set_data(json.dumps(data))
        return tmp


if __name__ == '__main__':
    app.run(debug=True)

【讨论】:

  • 非常感谢!!你能用按钮代替键盘按键吗?使用 Flask 似乎有点过头了,如果可能的话,我更喜欢 python 标准库。
  • 是的,您可以使用原始套接字进行客户端/服务器通信。您必须支持尝试从同一个套接字读取的多个客户端。这不是微不足道的,我很懒惰并使用flask创建了一个RESTful服务。
  • 我实现了你的方法,但它的可扩展性不是很好。有没有使用说flasksocketio的解决方案。所以,你不需要每次都发送请求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-13
相关资源
最近更新 更多