Initial commit
This commit is contained in:
parent
5dc2fc5881
commit
d23df9c51c
|
@ -0,0 +1,129 @@
|
|||
|
||||
import asyncio
|
||||
from socketio import AsyncSimpleClient
|
||||
from aiohttp import ClientSession
|
||||
from PIL import Image, ImageFont, ImageDraw, ImageFilter
|
||||
from base64 import b64decode
|
||||
from random import choice
|
||||
from json import load
|
||||
|
||||
class AsyncBotManager:
|
||||
def __init__(self, base: str = "https://onemillioncheckboxes.com"):
|
||||
self.base = base
|
||||
|
||||
self.canvas = Image.new("1", (1000, 1000))
|
||||
self.font = ImageFont.load_default(8)
|
||||
|
||||
self.difference: dict[int, bool] = {}
|
||||
self._shutdown: bool = False
|
||||
|
||||
def put_text(self, x: int, y: int, text: str):
|
||||
with Image.new("LA", (int(self.font.getlength(text) + 12), 16)) as im:
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.rectangle((0, 0, im.width, im.height), (0, 0))
|
||||
draw.text((6, 5), text, font=self.font, fill=(255, 0))
|
||||
|
||||
alpha = im.convert("L").filter(ImageFilter.MaxFilter(3))
|
||||
im.putalpha(alpha)
|
||||
self.put_image(x, y, im)
|
||||
|
||||
def put_image(self, ox: int, oy: int, im: Image.Image):
|
||||
for y in range(im.height):
|
||||
for x in range(im.width):
|
||||
l, a = im.getpixel((x, y)) # type: ignore
|
||||
if a:
|
||||
self.difference[x + ox + (y + oy) * 1000] = l > 0
|
||||
|
||||
|
||||
async def listener(self):
|
||||
async with ClientSession() as http:
|
||||
async with http.get(f"{self.base}/api/initial-state") as req:
|
||||
data = await req.json()
|
||||
buffer = b64decode(data["full_state"].encode() + b"=")
|
||||
self.canvas.paste(Image.frombytes("1", (1000, 1000), buffer))
|
||||
async with AsyncSimpleClient(http_session=http) as sio:
|
||||
await sio.connect(f"{self.base}/socket.io")
|
||||
while not self._shutdown:
|
||||
event, data = await sio.receive()
|
||||
if event == "full_state":
|
||||
buffer = b64decode(data["full_state"].encode() + b"=")
|
||||
image = Image.frombytes("1", (1000, 1000), buffer)
|
||||
self.canvas.paste(image)
|
||||
image.close()
|
||||
elif event == "batched_bit_toggles":
|
||||
bits_on, bits_off, timestamp = data
|
||||
for ndx in bits_on:
|
||||
y, x = divmod(ndx, 1000)
|
||||
self.canvas.putpixel((x, y), 255)
|
||||
for ndx in bits_off:
|
||||
y, x = divmod(ndx, 1000)
|
||||
self.canvas.putpixel((x, y), 0)
|
||||
else:
|
||||
print("unknown event", event, data)
|
||||
|
||||
async def writer(self):
|
||||
async with ClientSession() as http:
|
||||
async with AsyncSimpleClient(http_session=http) as sio:
|
||||
await sio.connect(f"{self.base}/socket.io")
|
||||
while not self._shutdown:
|
||||
try:
|
||||
event, data = await sio.receive(0.1)
|
||||
if event not in ("full_state", "batched_bit_toggles"):
|
||||
print("!!!", event, data)
|
||||
except Exception:
|
||||
pass
|
||||
index, target = choice(list(self.difference.items()))
|
||||
y, x = divmod(index, 1000)
|
||||
curr = self.canvas.getpixel((x, y)) > 0 # type: ignore
|
||||
if curr != target:
|
||||
print("swap", x, y, index)
|
||||
await sio.emit("toggle_bit", {
|
||||
"index": index
|
||||
})
|
||||
await asyncio.sleep(0.25)
|
||||
else:
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
async def __aenter__(self):
|
||||
self._listener_task = asyncio.create_task(self.listener())
|
||||
return self
|
||||
|
||||
async def __aexit__(self, a, b, c):
|
||||
self._shutdown = True
|
||||
await self._listener_task
|
||||
|
||||
|
||||
async def amain():
|
||||
with open("settings.json", "r") as fp:
|
||||
settings = load(fp)
|
||||
|
||||
async with AsyncBotManager() as mgr:
|
||||
mgr.font = ImageFont.truetype(settings["font"], 8)
|
||||
for elem in settings["elements"]:
|
||||
if elem["type"] == "text":
|
||||
mgr.put_text(elem["text"], elem["x"], elem["y"])
|
||||
elif elem["type"] == "image":
|
||||
with Image.open(elem["path"]).convert("LA") as im:
|
||||
mgr.put_image(elem["x"], elem["y"], im)
|
||||
elif elem["type"] == "rgb111":
|
||||
ox, oy = elem["x"], elem["y"]
|
||||
with Image.open(elem["path"]).convert("RGB") as im:
|
||||
for y in range(im.height):
|
||||
for x in range(im.width):
|
||||
r, g, b = im.getpixel((x, y)) # type: ignore
|
||||
pocket_x = x + ox
|
||||
pocket_y = y + oy
|
||||
|
||||
ndx_start = pocket_x * 3 + pocket_y * 577 * 3
|
||||
mgr.difference[ndx_start] = r > 128
|
||||
mgr.difference[ndx_start + 1] = g > 128
|
||||
mgr.difference[ndx_start + 2] = b > 128
|
||||
|
||||
await asyncio.gather(*[
|
||||
mgr.writer() for _ in range(settings["n_bots"])
|
||||
], return_exceptions=True)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(amain())
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
After Width: | Height: | Size: 763 B |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,111 @@
|
|||
from base64 import b64decode
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
from socketio import SimpleClient, exceptions
|
||||
from threading import Thread
|
||||
from requests import get
|
||||
|
||||
COLORS = [(0x33, 0x33, 0x66), (0x96, 0x96, 0xe0)]
|
||||
COLORS_UNPACKED = COLORS[0] + ((0, 0, 0) * 254) + COLORS[1]
|
||||
|
||||
|
||||
class App(tk.Tk):
|
||||
def __init__(self, url: str = "https://onemillioncheckboxes.com") -> None:
|
||||
self.url = url
|
||||
super().__init__()
|
||||
self.title("1M pixels")
|
||||
|
||||
self.sio = SimpleClient()
|
||||
|
||||
self._canvas = tk.Canvas(self)
|
||||
self._canvas.config(width=1000, height=1000, borderwidth=0, highlightthickness=0)
|
||||
self._canvas.pack()
|
||||
self.config(width=1000, height=1000, borderwidth=0, highlightthickness=0)
|
||||
|
||||
self._running = False
|
||||
|
||||
self._image = Image.new("RGB", (1000, 1000), COLORS[0])
|
||||
|
||||
self._canvas.bind("<Motion>", self._on_mouse_move)
|
||||
self._canvas.bind("<Button>", self._on_mouse_click)
|
||||
self.bind("<KeyPress>", self._on_key_down)
|
||||
|
||||
|
||||
def _on_mouse_move(self, event: tk.Event):
|
||||
self.title(f"1M pixels: {event.x}, {event.y}")
|
||||
|
||||
def _on_mouse_click(self, event: tk.Event):
|
||||
x, y = event.x, event.y
|
||||
self.sio.emit("toggle_bit", { "index": x + y * 1000 })
|
||||
|
||||
def _on_key_down(self, event: tk.Event):
|
||||
# <KeyPress event state=Control keysym=r keycode=27 char='\x12' x=538 y=556>
|
||||
if event.keysym == "r":
|
||||
print("FULL REFRESH")
|
||||
with get(f"{self.url}/api/initial-state") as req:
|
||||
buffer = b64decode(req.json()["full_state"].encode() + b"=")
|
||||
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
|
||||
img.putpalette(COLORS_UNPACKED)
|
||||
self._image.paste(img.convert("RGB"))
|
||||
print("FULL REFRESH DONE")
|
||||
|
||||
def _reader(self):
|
||||
while self._running:
|
||||
try:
|
||||
name, data = self.sio.receive()
|
||||
except exceptions.TimeoutError:
|
||||
print("Timeout")
|
||||
continue
|
||||
|
||||
if name == "batched_bit_toggles":
|
||||
bits_on, bits_off, timestamp = data
|
||||
for ndx in bits_on:
|
||||
y, x = divmod(ndx, 1000)
|
||||
self._image.putpixel((x, y), COLORS[1])
|
||||
for ndx in bits_off:
|
||||
y, x = divmod(ndx, 1000)
|
||||
self._image.putpixel((x, y), COLORS[0])
|
||||
self._tk_image.paste(self._image)
|
||||
print("partial update", len(bits_on), len(bits_off))
|
||||
|
||||
elif name == "full_state":
|
||||
print("full update")
|
||||
buffer = b64decode(data["full_state"].encode() + b"=")
|
||||
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
|
||||
img.putpalette(COLORS_UNPACKED)
|
||||
self._image.paste(img.convert("RGB"))
|
||||
self._tk_image.paste(self._image)
|
||||
|
||||
else:
|
||||
print(name, data)
|
||||
|
||||
self.sio.disconnect()
|
||||
print("_reader exited")
|
||||
|
||||
def _close(self):
|
||||
self._running = False
|
||||
print("waiting for reader to close")
|
||||
self._reader_thr.join()
|
||||
self.destroy()
|
||||
|
||||
def start(self):
|
||||
self.sio.connect(f"{self.url}/socket.io")
|
||||
|
||||
with get(f"{self.url}/api/initial-state") as req:
|
||||
buffer = b64decode(req.json()["full_state"].encode() + b"=")
|
||||
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
|
||||
img.putpalette(COLORS_UNPACKED)
|
||||
self._image.paste(img.convert("RGB"))
|
||||
self._tk_image = ImageTk.PhotoImage(self._image)
|
||||
self._tk_image_id = self._canvas.create_image(0, 0, anchor="nw", image=self._tk_image)
|
||||
self._running = True
|
||||
|
||||
self._reader_thr = Thread(target=self._reader, name="Reader thread")
|
||||
self._reader_thr.start()
|
||||
|
||||
self.protocol("WM_DELETE_WINDOW", self._close)
|
||||
self.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = App()
|
||||
app.start()
|
|
@ -1,5 +0,0 @@
|
|||
def main():
|
||||
print('Hi!')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1 +1,19 @@
|
|||
|
||||
aiohttp==3.9.5
|
||||
aiosignal==1.3.1
|
||||
attrs==23.2.0
|
||||
bidict==0.23.1
|
||||
certifi==2024.6.2
|
||||
charset-normalizer==3.3.2
|
||||
frozenlist==1.4.1
|
||||
h11==0.14.0
|
||||
idna==3.7
|
||||
multidict==6.0.5
|
||||
pillow==10.4.0
|
||||
python-engineio==4.9.1
|
||||
python-socketio==5.11.3
|
||||
requests==2.32.3
|
||||
simple-websocket==1.0.0
|
||||
urllib3==2.2.2
|
||||
websocket-client==1.8.0
|
||||
wsproto==1.2.0
|
||||
yarl==1.9.4
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
from base64 import b64decode
|
||||
from PIL import Image
|
||||
from requests import get
|
||||
|
||||
BASE_URL = "https://onemillioncheckboxes.com"
|
||||
|
||||
with get(f"{BASE_URL}/api/initial-state") as req:
|
||||
data = req.json()
|
||||
buffer = b64decode(data["full_state"].encode() + b"=")
|
||||
|
||||
def getbit(b: bytes, i: int) -> bool:
|
||||
return b[i // 8] & (0x80 >> (i % 8)) != 0
|
||||
|
||||
with Image.new("RGB", (577, 577)) as im:
|
||||
for y in range(im.height):
|
||||
for x in range(im.width):
|
||||
ndx_start = (x + y * 577) * 3
|
||||
im.putpixel(
|
||||
( x, y ),
|
||||
(
|
||||
255 if getbit(buffer, ndx_start) else 0,
|
||||
255 if getbit(buffer, ndx_start + 1) else 0,
|
||||
255 if getbit(buffer, ndx_start + 2) else 0,
|
||||
)
|
||||
)
|
||||
im.save("rgb111-full.png")
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"url": "https://onemillioncheckboxes.com",
|
||||
"font": "./ic8x8u.ttf",
|
||||
"proxies": [
|
||||
|
||||
],
|
||||
"n_bots": 10,
|
||||
"elements": [
|
||||
{
|
||||
"type": "rgb111",
|
||||
"path": "./casey111.png",
|
||||
"x": 240,
|
||||
"y": 240
|
||||
},
|
||||
{
|
||||
"type": "rgb111",
|
||||
"path": "./trans.png",
|
||||
"x": 200,
|
||||
"y": 200
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"path": "./casey.png",
|
||||
"x": 256,
|
||||
"y": 256
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"path": "./kangel.png",
|
||||
"x": 256,
|
||||
"y": 360
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"path": "./boykisser.png",
|
||||
"x": 69,
|
||||
"y": 420
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"path": "./lawbymike.png",
|
||||
"x": 150,
|
||||
"y": 420
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"path": "./colon3.png",
|
||||
"x": 333,
|
||||
"y": 333
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue