quantum-pixel 2.0.2__tar.gz → 2.0.4__tar.gz
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.
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/Cargo.lock +1 -1
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/Cargo.toml +1 -1
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/PKG-INFO +1 -1
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/generator.py +25 -20
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/encode.js +15 -2
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/decode.html +11 -1
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/encode.html +11 -1
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/web.py +30 -16
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/uv.lock +6 -6
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/FUNDING.yml +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/workflows/CI.yml +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/workflows/render-md-mermaid.yml +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.gitignore +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.python-version +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/LICENSE +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/README.md +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/README_PYPI.md +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/encoded.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/grok.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/intro.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/material.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/mermaid.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/preview.png +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/pyproject.toml +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/README_PYPI.md +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/__init__.py +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/main.py +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/decode.js +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/favicon.svg +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/robots.txt +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/styles.css +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/steganography.py +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/error.html +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/index.html +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/result.html +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/render.yaml +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/requirements.txt +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/src/lib.rs +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/tests/test_generator.py +0 -0
- {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/tests/test_steganography.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
edition = "2024"
|
|
3
3
|
name = "quantum-pixel"
|
|
4
|
-
version = "2.0.
|
|
4
|
+
version = "2.0.4"
|
|
5
5
|
authors = ["Linos1391 <linos.coding@gmail.com>"]
|
|
6
6
|
homepage = "https://github.com/Linos1391/quantum-pixel.git"
|
|
7
7
|
description = "When both YES and NO are existed. Only human with emotions can comprehend."
|
|
@@ -6,6 +6,7 @@ Generating layers from an image file.
|
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
8
|
from collections.abc import Generator as gen
|
|
9
|
+
import asyncio
|
|
9
10
|
from random import shuffle, randint
|
|
10
11
|
|
|
11
12
|
import numpy as np
|
|
@@ -45,28 +46,32 @@ class Generator:
|
|
|
45
46
|
"""
|
|
46
47
|
assert 0 <= intensity <= 1, "Invalid intensity"
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
np.arange(self.img_data.shape[
|
|
51
|
-
|
|
49
|
+
try:
|
|
50
|
+
# [[i, j] for i in range(img_data.shape[0]) for j in range(img_data.shape[1])]
|
|
51
|
+
available_location = np.stack(np.meshgrid(np.arange(self.img_data.shape[0]),
|
|
52
|
+
np.arange(self.img_data.shape[1])), axis=-1).reshape(-1, 2).tolist()
|
|
53
|
+
shuffle(available_location)
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
# do the shit.
|
|
56
|
+
self._allowance = int(int(np.sum(self.img_data) * intensity))
|
|
57
|
+
self._remain_allowance = self._allowance
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
layer: np.ndarray = np.zeros_like(self.img_data)
|
|
60
|
+
while self._remain_allowance > 0:
|
|
61
|
+
try:
|
|
62
|
+
location = tuple(available_location.pop())
|
|
63
|
+
except IndexError:
|
|
64
|
+
break
|
|
65
|
+
for current_value in range(3): # RGBA channels
|
|
66
|
+
value=min (randint(0, self.img_data[location][current_value]),
|
|
67
|
+
self._remain_allowance)
|
|
68
|
+
layer[location][current_value] = value
|
|
69
|
+
self._remain_allowance -= value
|
|
70
|
+
yield int(100*(1-self._remain_allowance/self._allowance))
|
|
71
|
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
72
|
+
Image.fromarray(layer.astype(np.uint8), "RGB").save(output_path)
|
|
73
|
+
except asyncio.exceptions.CancelledError:
|
|
74
|
+
yield -1
|
|
70
75
|
|
|
71
76
|
if __name__ == "__main__":
|
|
72
77
|
generator = Generator("assets/material.png")
|
|
@@ -29,10 +29,12 @@
|
|
|
29
29
|
result.style.display = "";
|
|
30
30
|
result.textContent = 'Loading, please wait patiently.';
|
|
31
31
|
|
|
32
|
+
const TIMEOUT = 300_000; // 5 minutes - NodeJS default anyway.
|
|
32
33
|
try {
|
|
33
34
|
const r = await fetch(location.pathname, {
|
|
34
35
|
method: 'POST',
|
|
35
|
-
body: fd
|
|
36
|
+
body: fd,
|
|
37
|
+
signal: AbortSignal.timeout(TIMEOUT)
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
const reader = r.body.getReader();
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
|
|
48
50
|
const error = document.getElementById("error");
|
|
49
51
|
if (json.error) {
|
|
52
|
+
error.style.display = "";
|
|
50
53
|
error.innerHTML = json.error;
|
|
51
54
|
} else {
|
|
52
55
|
error.style.display = "none";
|
|
@@ -74,7 +77,13 @@
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
} catch (err) {
|
|
77
|
-
|
|
80
|
+
if (err.name.startsWith("TimeoutError") || err.name.startsWith("AbortError")) {
|
|
81
|
+
const error = document.getElementById("error");
|
|
82
|
+
error.style.display = "";
|
|
83
|
+
error.innerHTML = "TimeoutError: The system has reach timeout of 5 minutes. This might be due to RAM limit exceed, use local system instead.";
|
|
84
|
+
} else {
|
|
85
|
+
console.error(err);
|
|
86
|
+
}
|
|
78
87
|
}
|
|
79
88
|
});
|
|
80
89
|
})
|
|
@@ -87,3 +96,7 @@
|
|
|
87
96
|
});
|
|
88
97
|
});
|
|
89
98
|
})();
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// TODO
|
|
102
|
+
// - error did not display initially! --> error.html wraps everything.
|
|
@@ -21,7 +21,17 @@
|
|
|
21
21
|
</form>
|
|
22
22
|
</section>
|
|
23
23
|
|
|
24
|
-
<
|
|
24
|
+
<br>
|
|
25
|
+
|
|
26
|
+
{% if error %}
|
|
27
|
+
<div id="error">
|
|
28
|
+
<h2>Error</h2>
|
|
29
|
+
<p>{{ error }}</p>
|
|
30
|
+
<p><a href="/">Return to Home</a></p>
|
|
31
|
+
</div>
|
|
32
|
+
{% else %}
|
|
33
|
+
<div id="error" style="display: none;"></div>
|
|
34
|
+
{% endif %}
|
|
25
35
|
|
|
26
36
|
<div id="result" style="display: none;"></div>
|
|
27
37
|
</main>
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
<img id="uploaded-image" src="/static/images/{{ uid }}/encode_input.png" alt="uploaded image">
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
{% if error %}
|
|
21
|
+
<div id="error">
|
|
22
|
+
<h2>Error</h2>
|
|
23
|
+
<p>{{ error }}</p>
|
|
24
|
+
<p><a href="/">Return to Home</a></p>
|
|
25
|
+
</div>
|
|
26
|
+
{% else %}
|
|
27
|
+
<div id="error" style="display: none;"></div>
|
|
28
|
+
{% endif %}
|
|
19
29
|
|
|
20
30
|
<section id="options-panel" class="options-panel">
|
|
21
31
|
<h3>Options</h3>
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import time
|
|
7
7
|
import asyncio
|
|
8
8
|
import json
|
|
9
|
+
import types
|
|
9
10
|
from concurrent.futures.thread import ThreadPoolExecutor
|
|
10
11
|
from contextlib import asynccontextmanager
|
|
11
12
|
|
|
@@ -120,9 +121,8 @@ async def start_encode(request: Request, uid: str):
|
|
|
120
121
|
"""Start the encode html."""
|
|
121
122
|
if not os.path.exists(_join_uid(uid, "encode_input.png")):
|
|
122
123
|
return templates.TemplateResponse("encode.html", {"request": request,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"Otherwise, there might be an attack/error, please issue me if that's so.")})})
|
|
124
|
+
"error": ("The image file cannot be found, you might just exit and got auto cleanup! "
|
|
125
|
+
"Otherwise, there might be an attack/error, please issue me if that's so.")})
|
|
126
126
|
return templates.TemplateResponse("encode.html", {"request": request, "uid": uid})
|
|
127
127
|
|
|
128
128
|
@app.post("/encode/{uid}")
|
|
@@ -144,16 +144,16 @@ async def end_encode(request: Request, uid: str):
|
|
|
144
144
|
future_item = asyncio.get_event_loop().run_in_executor(executor,
|
|
145
145
|
Generator(input_path).preview, float(form.get("intensity")),
|
|
146
146
|
_join_uid(uid, "encode_preview.png"))
|
|
147
|
-
future_item.add_done_callback(lambda _: _remove_from_list(uid))
|
|
148
147
|
background_task.update({uid: future_item})
|
|
149
148
|
|
|
150
149
|
async def _progress_streaming():
|
|
151
150
|
for progress in await future_item:
|
|
152
151
|
yield f"{progress}\n"
|
|
152
|
+
_remove_from_list(uid)
|
|
153
153
|
yield json.dumps({"result": templates.get_template("result.html")
|
|
154
154
|
.render({"path": f"{uid}/encode_preview.png",
|
|
155
155
|
"download": "encode-preview",})})
|
|
156
|
-
|
|
156
|
+
return StreamingResponse(_progress_streaming(), media_type="text/plain")
|
|
157
157
|
|
|
158
158
|
except asyncio.exceptions.CancelledError:
|
|
159
159
|
return_error = "User exited."
|
|
@@ -162,14 +162,21 @@ async def end_encode(request: Request, uid: str):
|
|
|
162
162
|
|
|
163
163
|
case "panel_resize":
|
|
164
164
|
if input_path and form.get("image") and form.get("width") and form.get("length"):
|
|
165
|
-
|
|
165
|
+
def _resize() -> None:
|
|
166
|
+
Image.open(form.get("image").file)\
|
|
166
167
|
.resize((int(form.get("width")), int(form.get("length"))))\
|
|
167
168
|
.save(_join_uid(uid, "encode_resize.png"), optimize=True)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
try:
|
|
170
|
+
future_item=asyncio.get_event_loop().run_in_executor(executor, _resize)
|
|
171
|
+
background_task.update({uid: future_item})
|
|
172
|
+
await future_item
|
|
173
|
+
return JSONResponse(content={
|
|
174
|
+
"result": templates.get_template("result.html").render({
|
|
175
|
+
"path": f"{uid}/encode_resize.png",
|
|
176
|
+
"download": "encode-resize",
|
|
177
|
+
})})
|
|
178
|
+
except asyncio.exceptions.CancelledError:
|
|
179
|
+
return_error = "User exited."
|
|
173
180
|
|
|
174
181
|
case "panel_steganography":
|
|
175
182
|
if input_path and form.get("disguise"):
|
|
@@ -206,9 +213,8 @@ async def start_decode(request: Request, uid: str):
|
|
|
206
213
|
"""Start the decode html."""
|
|
207
214
|
if not os.path.exists(_join_uid(uid, "decode_input.png")):
|
|
208
215
|
return templates.TemplateResponse("decode.html", {"request": request,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"Otherwise, there might be an attack/error, please issue me if that's so.")})})
|
|
216
|
+
"error": ("The image file cannot be found, you might just exit and got auto cleanup! "
|
|
217
|
+
"Otherwise, there might be an attack/error, please issue me if that's so.")})
|
|
212
218
|
return templates.TemplateResponse("decode.html", {"request": request})
|
|
213
219
|
|
|
214
220
|
@app.post("/decode/{uid}", response_class=JSONResponse)
|
|
@@ -262,6 +268,14 @@ async def end_decode(request: Request, uid: str):
|
|
|
262
268
|
async def remove(uid: str):
|
|
263
269
|
"""Remove file when user exit."""
|
|
264
270
|
shutil.rmtree(os.path.join(IMAGE_DIR, uid), ignore_errors=True)
|
|
265
|
-
task = background_task.get(uid)
|
|
271
|
+
task: asyncio.Future = background_task.get(uid)
|
|
266
272
|
if task:
|
|
267
|
-
|
|
273
|
+
try:
|
|
274
|
+
result = task.result()
|
|
275
|
+
if isinstance(result, types.GeneratorType):
|
|
276
|
+
result.throw(asyncio.exceptions.CancelledError("Task cancelled."))
|
|
277
|
+
_remove_from_list(uid)
|
|
278
|
+
else:
|
|
279
|
+
task.cancel()
|
|
280
|
+
except (asyncio.exceptions.InvalidStateError, asyncio.exceptions.CancelledError):
|
|
281
|
+
task.cancel()
|
|
@@ -125,7 +125,7 @@ wheels = [
|
|
|
125
125
|
|
|
126
126
|
[[package]]
|
|
127
127
|
name = "fastapi"
|
|
128
|
-
version = "0.
|
|
128
|
+
version = "0.131.0"
|
|
129
129
|
source = { registry = "https://pypi.org/simple" }
|
|
130
130
|
dependencies = [
|
|
131
131
|
{ name = "annotated-doc" },
|
|
@@ -134,9 +134,9 @@ dependencies = [
|
|
|
134
134
|
{ name = "typing-extensions" },
|
|
135
135
|
{ name = "typing-inspection" },
|
|
136
136
|
]
|
|
137
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
137
|
+
sdist = { url = "https://files.pythonhosted.org/packages/91/32/158cbf685b7d5a26f87131069da286bf10fc9fbf7fc968d169d48a45d689/fastapi-0.131.0.tar.gz", hash = "sha256:6531155e52bee2899a932c746c9a8250f210e3c3303a5f7b9f8a808bfe0548ff", size = 369612, upload-time = "2026-02-22T16:38:11.252Z" }
|
|
138
138
|
wheels = [
|
|
139
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/ff/94/b58ec24c321acc2ad1327f69b033cadc005e0f26df9a73828c9e9c7db7ce/fastapi-0.131.0-py3-none-any.whl", hash = "sha256:ed0e53decccf4459de78837ce1b867cd04fa9ce4579497b842579755d20b405a", size = 103854, upload-time = "2026-02-22T16:38:09.814Z" },
|
|
140
140
|
]
|
|
141
141
|
|
|
142
142
|
[package.optional-dependencies]
|
|
@@ -739,7 +739,7 @@ wheels = [
|
|
|
739
739
|
|
|
740
740
|
[[package]]
|
|
741
741
|
name = "typer"
|
|
742
|
-
version = "0.24.
|
|
742
|
+
version = "0.24.1"
|
|
743
743
|
source = { registry = "https://pypi.org/simple" }
|
|
744
744
|
dependencies = [
|
|
745
745
|
{ name = "annotated-doc" },
|
|
@@ -747,9 +747,9 @@ dependencies = [
|
|
|
747
747
|
{ name = "rich" },
|
|
748
748
|
{ name = "shellingham" },
|
|
749
749
|
]
|
|
750
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
750
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
|
|
751
751
|
wheels = [
|
|
752
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
752
|
+
{ url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
|
|
753
753
|
]
|
|
754
754
|
|
|
755
755
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|