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.
Files changed (40) hide show
  1. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/Cargo.lock +1 -1
  2. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/Cargo.toml +1 -1
  3. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/PKG-INFO +1 -1
  4. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/generator.py +25 -20
  5. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/encode.js +15 -2
  6. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/decode.html +11 -1
  7. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/encode.html +11 -1
  8. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/web.py +30 -16
  9. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/uv.lock +6 -6
  10. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/FUNDING.yml +0 -0
  11. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/workflows/CI.yml +0 -0
  12. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.github/workflows/render-md-mermaid.yml +0 -0
  13. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.gitignore +0 -0
  14. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/.python-version +0 -0
  15. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/LICENSE +0 -0
  16. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/README.md +0 -0
  17. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/README_PYPI.md +0 -0
  18. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/encoded.png +0 -0
  19. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/grok.png +0 -0
  20. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/intro.png +0 -0
  21. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/material.png +0 -0
  22. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/mermaid.png +0 -0
  23. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/assets/preview.png +0 -0
  24. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/pyproject.toml +0 -0
  25. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/README_PYPI.md +0 -0
  26. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/__init__.py +0 -0
  27. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/main.py +0 -0
  28. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/decode.js +0 -0
  29. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/favicon.svg +0 -0
  30. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/robots.txt +0 -0
  31. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/static/styles.css +0 -0
  32. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/steganography.py +0 -0
  33. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/error.html +0 -0
  34. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/index.html +0 -0
  35. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/quantum_pixel/templates/result.html +0 -0
  36. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/render.yaml +0 -0
  37. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/requirements.txt +0 -0
  38. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/src/lib.rs +0 -0
  39. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/tests/test_generator.py +0 -0
  40. {quantum_pixel-2.0.2 → quantum_pixel-2.0.4}/tests/test_steganography.py +0 -0
@@ -773,7 +773,7 @@ dependencies = [
773
773
 
774
774
  [[package]]
775
775
  name = "quantum-pixel"
776
- version = "2.0.2"
776
+ version = "2.0.4"
777
777
  dependencies = [
778
778
  "pyo3",
779
779
  "stegano-core",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  edition = "2024"
3
3
  name = "quantum-pixel"
4
- version = "2.0.2"
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."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantum-pixel
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Natural Language :: English
6
6
  Classifier: Programming Language :: Rust
@@ -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
- # [[i, j] for i in range(img_data.shape[0]) for j in range(img_data.shape[1])]
49
- available_location = np.stack(np.meshgrid(np.arange(self.img_data.shape[0]),
50
- np.arange(self.img_data.shape[1])), axis=-1).reshape(-1, 2).tolist()
51
- shuffle(available_location)
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
- # do the shit.
54
- self._allowance = int(int(np.sum(self.img_data) * intensity))
55
- self._remain_allowance = self._allowance
55
+ # do the shit.
56
+ self._allowance = int(int(np.sum(self.img_data) * intensity))
57
+ self._remain_allowance = self._allowance
56
58
 
57
- layer: np.ndarray = np.zeros_like(self.img_data)
58
- while self._remain_allowance > 0:
59
- try:
60
- location = tuple(available_location.pop())
61
- except IndexError:
62
- break
63
- for current_value in range(3): # RGBA channels
64
- value=min(randint(0, self.img_data[location][current_value]),self._remain_allowance)
65
- layer[location][current_value] = value
66
- self._remain_allowance -= value
67
- yield int(100*(1-self._remain_allowance/self._allowance))
68
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
69
- Image.fromarray(layer.astype(np.uint8), "RGB").save(output_path)
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
- console.error(err);
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
- <div id="error" style="display: none;">{{ error | safe }}</div>
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
- <div id="error" style="display: none;">{{ error | safe }}</div>
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
- "error": templates.get_template("error.html").render({"error":
124
- ("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.")})})
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
- return StreamingResponse(_progress_streaming(), media_type="text/plain")
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
- Image.open(form.get("image").file)\
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
- return JSONResponse(content={
169
- "result": templates.get_template("result.html").render({
170
- "path": f"{uid}/encode_resize.png",
171
- "download": "encode-resize",
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
- "error": templates.get_template("error.html").render({"error":
210
- ("The image file cannot be found, you might just exit and got auto cleanup! "
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
- task.cancel()
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.129.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/48/47/75f6bea02e797abff1bca968d5997793898032d9923c1935ae2efdece642/fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af", size = 375450, upload-time = "2026-02-12T13:54:52.541Z" }
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/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" },
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.0"
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/5a/b6/3e681d3b6bb22647509bdbfdd18055d5adc0dce5c5585359fa46ff805fdc/typer-0.24.0.tar.gz", hash = "sha256:f9373dc4eff901350694f519f783c29b6d7a110fc0dcc11b1d7e353b85ca6504", size = 118380, upload-time = "2026-02-16T22:08:48.496Z" }
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/85/d0/4da85c2a45054bb661993c93524138ace4956cb075a7ae0c9d1deadc331b/typer-0.24.0-py3-none-any.whl", hash = "sha256:5fc435a9c8356f6160ed6e85a6301fdd6e3d8b2851da502050d1f92c5e9eddc8", size = 56441, upload-time = "2026-02-16T22:08:47.535Z" },
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