ipysketch-lite 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ from http.server import BaseHTTPRequestHandler, HTTPServer
2
+ from IPython.display import HTML, display
3
+ from .gen import template
4
+ import threading
5
+ import base64
6
+ import io
7
+
8
+ try:
9
+ from PIL import Image
10
+ import numpy as np
11
+
12
+ PIL_INSTALLED = True
13
+ except ImportError:
14
+ PIL_INSTALLED = False
15
+
16
+
17
+ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
18
+ def log_message(self, format, *args):
19
+ pass
20
+
21
+ def do_POST(self):
22
+ content_length = int(self.headers["Content-Length"])
23
+ post_data = self.rfile.read(content_length)
24
+ message = post_data.decode("utf-8")
25
+
26
+ global output
27
+ output = message
28
+
29
+ global response_received
30
+ response_received.set()
31
+
32
+ self.send_response(200)
33
+
34
+
35
+ def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=5000):
36
+ server_address = ("", port)
37
+ httpd = server_class(server_address, handler_class)
38
+ server_thread = threading.Thread(target=httpd.serve_forever)
39
+ server_thread.start()
40
+
41
+
42
+ class Sketch:
43
+ def __init__(self, width: int = 400, height: int = 300):
44
+ global response_received
45
+ response_received = threading.Event()
46
+ run()
47
+
48
+ metadata = {
49
+ "{width}": width,
50
+ "{height}": height,
51
+ }
52
+
53
+ sketch_template = template
54
+ for key, value in metadata.items():
55
+ sketch_template = sketch_template.replace(key, str(value))
56
+
57
+ display(HTML(sketch_template))
58
+ response_received.wait()
59
+
60
+ def get_output(self) -> str:
61
+ return output
62
+
63
+ def get_output_array(self) -> np.ndarray:
64
+ if PIL_INSTALLED:
65
+ image_data = output.split(",")[1]
66
+ image = Image.open(io.BytesIO(base64.b64decode(image_data)))
67
+ return np.array(image)
68
+ else:
69
+ raise ImportError("PIL (Pillow) and NumPy are required to use this method.")
@@ -0,0 +1,196 @@
1
+ template = """<style>
2
+ #toggleFill {
3
+ background-color: #f0f0f0;
4
+ border: 1px solid #999999;
5
+ }
6
+
7
+ #toggleFill.active {
8
+ background-color: #ffffff;
9
+ border: 1px solid #000000;
10
+ }
11
+ </style>
12
+ <canvas
13
+ id="canvas"
14
+ width="{width}"
15
+ height="{height}"
16
+ style="border: 2px solid black"
17
+ ></canvas>
18
+ <button id="toggleFill" onclick="toggleFill()">bucket</button>
19
+ <button onclick="clearCanvas()">clear</button>
20
+ <input id="color" type="color" value="#000000" />
21
+ <script>
22
+ var canvas = document.getElementById("canvas");
23
+ var colorInput = document.getElementById("color");
24
+ var toggleFillButton = document.getElementById("toggleFill");
25
+ var ctx = canvas.getContext("2d", { willReadFrequently: true });
26
+ ctx.lineWidth = 4;
27
+ ctx.lineJoin = "round";
28
+ var drawing = false;
29
+ var mouse = { x: 0, y: 0 };
30
+ var fillMode = false;
31
+
32
+ function canvasUpload() {
33
+ fetch("http://localhost:5000", {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "text/plain",
37
+ },
38
+ body: canvas.toDataURL(),
39
+ });
40
+ }
41
+
42
+ canvasUpload();
43
+
44
+ function toggleFill() {
45
+ fillMode = !fillMode;
46
+ toggleFillButton.classList.toggle("active", fillMode);
47
+ }
48
+
49
+ function hexToRgb(hex) {
50
+ return {
51
+ r: parseInt(hex.substring(1, 3), 16),
52
+ g: parseInt(hex.substring(3, 5), 16),
53
+ b: parseInt(hex.substring(5, 7), 16),
54
+ a: 255,
55
+ };
56
+ }
57
+
58
+ canvas.addEventListener("mousedown", (e) => {
59
+ if (fillMode) {
60
+ floodFill(hexToRgb(colorInput.value), e.offsetX, e.offsetY);
61
+ return;
62
+ }
63
+
64
+ mouse = { x: e.offsetX, y: e.offsetY };
65
+ drawing = true;
66
+ });
67
+
68
+ canvas.addEventListener("mousemove", (e) => {
69
+ if (drawing) {
70
+ drawLine(ctx, mouse.x, mouse.y, e.offsetX, e.offsetY);
71
+ mouse = { x: e.offsetX, y: e.offsetY };
72
+ }
73
+ });
74
+
75
+ canvas.addEventListener("mouseup", (e) => {
76
+ if (drawing) {
77
+ drawLine(ctx, mouse.x, mouse.y, e.offsetX, e.offsetY);
78
+ drawing = false;
79
+ canvasUpload();
80
+ }
81
+ });
82
+
83
+ function drawLine(context, x1, y1, x2, y2) {
84
+ ctx.strokeStyle = colorInput.value;
85
+ ctx.beginPath();
86
+ ctx.moveTo(x1, y1);
87
+ ctx.lineTo(x2, y2);
88
+ ctx.closePath();
89
+ ctx.stroke();
90
+ }
91
+
92
+ function clearCanvas() {
93
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
94
+ }
95
+
96
+ function floodFill(color, x, y) {
97
+ var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
98
+ const { width, height, data } = imgData;
99
+ if (pixelColMatch(x, y, color, data, width)) return;
100
+
101
+ const stack = [[x, y]];
102
+ let baseIdx = (width * y + x) * 4;
103
+ const oColor = {
104
+ r: data[baseIdx],
105
+ g: data[baseIdx + 1],
106
+ b: data[baseIdx + 2],
107
+ a: data[baseIdx + 3],
108
+ };
109
+
110
+ while (stack.length) {
111
+ var [cx, cy] = stack.pop();
112
+ const move = (dx, dy) => {
113
+ let nx = cx + dx;
114
+ let ny = cy + dy;
115
+ while (
116
+ ny >= 0 &&
117
+ ny < height &&
118
+ pixelColMatch(nx, ny, oColor, data, width)
119
+ ) {
120
+ setPixelCol(nx, ny, color, data, width);
121
+ stack.push([nx, ny]);
122
+ ny += dy;
123
+ }
124
+ };
125
+ move(0, 1);
126
+ move(0, -1);
127
+ move(-1, 0);
128
+ move(1, 0);
129
+ }
130
+ ctx.putImageData(imgData, 0, 0);
131
+ }
132
+
133
+ function pixelColMatch(x, y, color, data, width) {
134
+ var baseIdx = (width * y + x) * 4;
135
+ return (
136
+ data[baseIdx] === color.r &&
137
+ data[baseIdx + 1] === color.g &&
138
+ data[baseIdx + 2] === color.b &&
139
+ data[baseIdx + 3] === color.a
140
+ );
141
+ }
142
+
143
+ function setPixelCol(x, y, color, data, width) {
144
+ var baseIdx = (width * y + x) * 4;
145
+ data[baseIdx] = color.r & 0xff;
146
+ data[baseIdx + 1] = color.g & 0xff;
147
+ data[baseIdx + 2] = color.b & 0xff;
148
+ data[baseIdx + 3] = color.a & 0xff;
149
+ }
150
+
151
+ var rect = canvas.getBoundingClientRect();
152
+ var offset = { x: rect.left, y: rect.top };
153
+ var touches = [];
154
+
155
+ canvas.addEventListener("touchstart", (e) => {
156
+ e.preventDefault();
157
+ rect = canvas.getBoundingClientRect();
158
+ offset = { x: rect.left, y: rect.top };
159
+ touches = Array.from(e.touches);
160
+
161
+ if (fillMode) {
162
+ var x = Math.floor(touches[0].clientX - offset.x);
163
+ var y = Math.floor(touches[0].clientY - offset.y);
164
+ floodFill(hexToRgb(colorInput.value), x, y);
165
+ }
166
+ });
167
+
168
+ canvas.addEventListener("touchend", (e) => {
169
+ canvasUpload();
170
+ });
171
+
172
+ canvas.addEventListener("touchmove", (e) => {
173
+ if (fillMode) return;
174
+
175
+ e.preventDefault();
176
+ rect = canvas.getBoundingClientRect();
177
+ offset = { x: rect.left, y: rect.top };
178
+ for (var i = 0; i < e.changedTouches.length; i++) {
179
+ var touch = e.changedTouches[i];
180
+ var previousTouch = touches.find(
181
+ (t) => t.identifier === touch.identifier,
182
+ );
183
+ if (previousTouch) {
184
+ drawLine(
185
+ ctx,
186
+ previousTouch.clientX - offset.x,
187
+ previousTouch.clientY - offset.y,
188
+ touch.clientX - offset.x,
189
+ touch.clientY - offset.y,
190
+ );
191
+ }
192
+ touches.splice(i, 1, touch);
193
+ }
194
+ });
195
+ </script>
196
+ """
@@ -0,0 +1 @@
1
+ Copyright (c) 2024 Matthew Andre Taylor
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: ipysketch-lite
3
+ Version: 0.0.1
4
+ Summary: Simple sketching tool for jupyter notebooks
5
+ Author-email: Matthew Taylor <matthew.taylor.andre@gmail.com>
6
+ Project-URL: Homepage, https://github.com/MatthewAndreTaylor/ipysketch_lite
7
+ Keywords: jupyter,sketch,drawing,canvas
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Provides-Extra: utils
11
+ Requires-Dist: numpy ; extra == 'utils'
12
+ Requires-Dist: pillow ; extra == 'utils'
13
+
14
+ # ipysketch_lite
15
+
16
+ A lite sketching utility for python notebooks, no sockets or extra dependencies 🎨
17
+
18
+ (no extra widget code)
19
+
20
+ Make sketches right in your notebook then use the sketch in your python code.
21
+
22
+ ## Quickstart
23
+
24
+ Start drawing a quick sketch in your notebook like this
25
+
26
+ ```py
27
+ from ipysketch_lite import Sketch
28
+
29
+ sketch = Sketch()
30
+ ```
31
+
32
+ Then add a new cell to retrieve the sketch in python
33
+
34
+ ```py
35
+ print(sketch.get_output())
36
+
37
+ import matplotlib.pyplot as plt
38
+
39
+ plt.imshow(sketch.get_output_array())
40
+ ```
41
+
42
+ ![example sketch](examples/sketches/example.png)
43
+
44
+ Sketches get updated in cells after draw updates
45
+
46
+ This means you can continue your sketch and get the new updated outputs
@@ -0,0 +1,7 @@
1
+ ipysketch_lite/__init__.py,sha256=-8lSU7nAsGHEhPlo1Civ8__miQjT7ZQ8kdpj9NlVskM,1876
2
+ ipysketch_lite/gen/__init__.py,sha256=3J5KuxXfM5WpeiTnFrEFmGq_vMmSAWMWFnK47nXD2mk,4967
3
+ ipysketch_lite-0.0.1.dist-info/LICENSE,sha256=-guCcX4glX_hMB9LpAtB_If_h0xpXsm0wvc55655x6M,39
4
+ ipysketch_lite-0.0.1.dist-info/METADATA,sha256=yQLg9Wesap4-G-8DAoLOxY1DrvfHZHtOiWNI7VK8m1M,1134
5
+ ipysketch_lite-0.0.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
6
+ ipysketch_lite-0.0.1.dist-info/top_level.txt,sha256=IGsNv82_nxTmStOol0QKT3whl6kwu6czDb-yOUCkW14,15
7
+ ipysketch_lite-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (71.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ipysketch_lite