Quickbend 1.0.0__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.
- quickbend-1.0.0/PKG-INFO +21 -0
- quickbend-1.0.0/README.md +3 -0
- quickbend-1.0.0/pyproject.toml +30 -0
- quickbend-1.0.0/setup.cfg +4 -0
- quickbend-1.0.0/src/Quickbend/__init__.py +0 -0
- quickbend-1.0.0/src/Quickbend/app.py +152 -0
- quickbend-1.0.0/src/Quickbend/databend.py +214 -0
- quickbend-1.0.0/src/Quickbend/files.py +33 -0
- quickbend-1.0.0/src/Quickbend.egg-info/PKG-INFO +21 -0
- quickbend-1.0.0/src/Quickbend.egg-info/SOURCES.txt +12 -0
- quickbend-1.0.0/src/Quickbend.egg-info/dependency_links.txt +1 -0
- quickbend-1.0.0/src/Quickbend.egg-info/entry_points.txt +2 -0
- quickbend-1.0.0/src/Quickbend.egg-info/requires.txt +7 -0
- quickbend-1.0.0/src/Quickbend.egg-info/top_level.txt +1 -0
quickbend-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Quickbend
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: an app to create glitchart using pyside6
|
|
5
|
+
Author-email: Daniel Crutti <dancrutti@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: audioop-lts==0.2.2
|
|
12
|
+
Requires-Dist: numpy==2.4.6
|
|
13
|
+
Requires-Dist: pillow==12.2.0
|
|
14
|
+
Requires-Dist: PySide6==6.11.1
|
|
15
|
+
Requires-Dist: PySide6_Addons==6.11.1
|
|
16
|
+
Requires-Dist: PySide6_Essentials==6.11.1
|
|
17
|
+
Requires-Dist: shiboken6==6.11.1
|
|
18
|
+
|
|
19
|
+
# Quickbend
|
|
20
|
+
|
|
21
|
+
**Quickbend** is a tool for creating art via databending with images. It uses interpreting bmp files as logarithmic audio encoding schemes (a-law/u-law/adpcm) and then reconverting those back to bmp to create warped and glitched images.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Quickbend"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "an app to create glitchart using pyside6"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Daniel Crutti", email = "dancrutti@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.9"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"audioop-lts==0.2.2",
|
|
21
|
+
"numpy==2.4.6",
|
|
22
|
+
"pillow==12.2.0",
|
|
23
|
+
"PySide6==6.11.1",
|
|
24
|
+
"PySide6_Addons==6.11.1",
|
|
25
|
+
"PySide6_Essentials==6.11.1",
|
|
26
|
+
"shiboken6==6.11.1"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
Quickbend = "Quickbend.app:main"
|
|
File without changes
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from PySide6.QtCore import Qt
|
|
5
|
+
from PySide6.QtGui import QPixmap
|
|
6
|
+
from PySide6.QtWidgets import (
|
|
7
|
+
QApplication,
|
|
8
|
+
QComboBox,
|
|
9
|
+
QHBoxLayout,
|
|
10
|
+
QLabel,
|
|
11
|
+
QLineEdit,
|
|
12
|
+
QMainWindow,
|
|
13
|
+
QPushButton,
|
|
14
|
+
QVBoxLayout,
|
|
15
|
+
QWidget,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .databend import databend
|
|
19
|
+
from .files import browse_file, save_file
|
|
20
|
+
|
|
21
|
+
cleanup_paths = []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# main window
|
|
25
|
+
class MainWindow(QMainWindow):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
# main class
|
|
28
|
+
super().__init__()
|
|
29
|
+
|
|
30
|
+
self.setWindowTitle("My App")
|
|
31
|
+
layout = QVBoxLayout()
|
|
32
|
+
|
|
33
|
+
# button that opens file selection dialog
|
|
34
|
+
self.file_button = QPushButton("Get Files:")
|
|
35
|
+
self.file_button.clicked.connect(self.LoadNewImage)
|
|
36
|
+
layout.addWidget(self.file_button)
|
|
37
|
+
|
|
38
|
+
self.encoding_box = QComboBox()
|
|
39
|
+
self.encoding_box.addItems(["alaw", "ulaw", "adpcm"])
|
|
40
|
+
self.encoding_box_label = QLabel("Select Encoding:")
|
|
41
|
+
encoding_layout = QHBoxLayout()
|
|
42
|
+
|
|
43
|
+
encoding_layout.addWidget(self.encoding_box_label)
|
|
44
|
+
encoding_layout.addWidget(self.encoding_box, stretch=1)
|
|
45
|
+
layout.addLayout(encoding_layout)
|
|
46
|
+
|
|
47
|
+
self.xor_box = QComboBox()
|
|
48
|
+
self.xor_box.addItems(["Off", "On"])
|
|
49
|
+
self.xor_box_label = QLabel("Xor Mixing:")
|
|
50
|
+
xor_layout = QHBoxLayout()
|
|
51
|
+
|
|
52
|
+
xor_layout.addWidget(self.xor_box_label)
|
|
53
|
+
xor_layout.addWidget(self.xor_box, stretch=1)
|
|
54
|
+
layout.addLayout(xor_layout)
|
|
55
|
+
|
|
56
|
+
self.echo_time = QLineEdit()
|
|
57
|
+
self.echo_time.setPlaceholderText("Enter a Number (Decimals Accepted)")
|
|
58
|
+
self.echo_time.setInputMask("00.00;_")
|
|
59
|
+
self.echo_time_label = QLabel("Echo Time (Leave blank for no echo):")
|
|
60
|
+
echo_layout = QHBoxLayout()
|
|
61
|
+
|
|
62
|
+
echo_layout.addWidget(self.echo_time_label)
|
|
63
|
+
echo_layout.addWidget(self.echo_time, stretch=1)
|
|
64
|
+
|
|
65
|
+
layout.addLayout(echo_layout)
|
|
66
|
+
|
|
67
|
+
# image from file selection
|
|
68
|
+
self.image_preview = QLabel()
|
|
69
|
+
self.original_pixmap = QPixmap("placeholder.jpg")
|
|
70
|
+
layout.addWidget(self.image_preview)
|
|
71
|
+
|
|
72
|
+
save_button = QPushButton("export image")
|
|
73
|
+
save_button.clicked.connect(self.export_handler)
|
|
74
|
+
layout.addWidget(save_button)
|
|
75
|
+
|
|
76
|
+
# container to hold all widgets
|
|
77
|
+
container = QWidget()
|
|
78
|
+
container.setLayout(layout)
|
|
79
|
+
|
|
80
|
+
self.setCentralWidget(container)
|
|
81
|
+
|
|
82
|
+
# handles window resizes and keeps aspect ratio
|
|
83
|
+
def resizeEvent(self, event):
|
|
84
|
+
super().resizeEvent(event)
|
|
85
|
+
scaled_pixmap = self.original_pixmap.scaled(
|
|
86
|
+
self.image_preview.size(),
|
|
87
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
|
88
|
+
Qt.TransformationMode.SmoothTransformation,
|
|
89
|
+
)
|
|
90
|
+
self.image_preview.setPixmap(scaled_pixmap)
|
|
91
|
+
self.image_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
92
|
+
|
|
93
|
+
# loads image from file dialog into pixmap of QLabel
|
|
94
|
+
def LoadNewImage(self):
|
|
95
|
+
path = browse_file()
|
|
96
|
+
# gets paths for program cleanup later.
|
|
97
|
+
global cleanup_paths
|
|
98
|
+
cleanup_paths = path
|
|
99
|
+
if self.xor_box.currentText() == "On":
|
|
100
|
+
xor_flag = True
|
|
101
|
+
else:
|
|
102
|
+
xor_flag = False
|
|
103
|
+
if path:
|
|
104
|
+
try:
|
|
105
|
+
delay_time = float(self.echo_time.text())
|
|
106
|
+
except ValueError:
|
|
107
|
+
delay_time = 0
|
|
108
|
+
synced_files = databend(
|
|
109
|
+
path,
|
|
110
|
+
"/tmp/mash.bmp",
|
|
111
|
+
encoding=self.encoding_box.currentText(),
|
|
112
|
+
use_xor=xor_flag,
|
|
113
|
+
delay_time=delay_time,
|
|
114
|
+
)
|
|
115
|
+
if synced_files:
|
|
116
|
+
cleanup_paths.extend(synced_files)
|
|
117
|
+
self.original_pixmap = QPixmap("/tmp/mash.bmp")
|
|
118
|
+
scaled_pixmap = self.original_pixmap.scaled(
|
|
119
|
+
self.image_preview.size(),
|
|
120
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
|
121
|
+
Qt.TransformationMode.SmoothTransformation,
|
|
122
|
+
)
|
|
123
|
+
self.image_preview.setPixmap(scaled_pixmap)
|
|
124
|
+
|
|
125
|
+
def export_handler(self):
|
|
126
|
+
(save_file_path) = save_file()
|
|
127
|
+
if save_file_path:
|
|
128
|
+
shutil.move("/tmp/mash.bmp", save_file_path)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def cleanup():
|
|
132
|
+
if os.path.exists("/tmp/mash.bmp"):
|
|
133
|
+
os.remove("/tmp/mash.bmp")
|
|
134
|
+
for file in cleanup_paths:
|
|
135
|
+
if os.path.exists(file):
|
|
136
|
+
os.remove(file)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# open and show window
|
|
140
|
+
def main():
|
|
141
|
+
global app, window
|
|
142
|
+
app = QApplication()
|
|
143
|
+
|
|
144
|
+
app.aboutToQuit.connect(cleanup)
|
|
145
|
+
|
|
146
|
+
window = MainWindow()
|
|
147
|
+
window.show()
|
|
148
|
+
|
|
149
|
+
app.exec()
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import audioop
|
|
3
|
+
import os
|
|
4
|
+
import struct
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def databend(input_files, output_file, encoding="alaw", use_xor=False, delay_time=0.0):
|
|
11
|
+
if not input_files:
|
|
12
|
+
print("No input files provided.")
|
|
13
|
+
return []
|
|
14
|
+
|
|
15
|
+
# filter out non-existent files
|
|
16
|
+
valid_files = [f for f in input_files if os.path.exists(f)]
|
|
17
|
+
if not valid_files:
|
|
18
|
+
print("No valid input files found.")
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
# get dimensions and areas to find the lowest resolution
|
|
22
|
+
file_dims = {}
|
|
23
|
+
min_area = float("inf")
|
|
24
|
+
target_w, target_h = None, None
|
|
25
|
+
target_file = None
|
|
26
|
+
|
|
27
|
+
for f in valid_files:
|
|
28
|
+
try:
|
|
29
|
+
with Image.open(f) as img:
|
|
30
|
+
w, h = img.size
|
|
31
|
+
area = w * h
|
|
32
|
+
file_dims[f] = (w, h, area)
|
|
33
|
+
if area < min_area:
|
|
34
|
+
min_area = area
|
|
35
|
+
target_w, target_h = w, h
|
|
36
|
+
target_file = f
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Error reading dimensions for {f}: {e}")
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
# downscale higher resolution files to match the lowest resolution file
|
|
42
|
+
synced_files = []
|
|
43
|
+
created_synced_files = [] # array to track newly generated downscaled files
|
|
44
|
+
|
|
45
|
+
for f in valid_files:
|
|
46
|
+
w, h, area = file_dims[f]
|
|
47
|
+
if area > min_area:
|
|
48
|
+
synced_name = f"{os.path.splitext(f)[0]}_synced.bmp"
|
|
49
|
+
print(
|
|
50
|
+
f"Downscaling {f} ({w}x{h}) to match {target_file} ({target_w}x{target_h})..."
|
|
51
|
+
)
|
|
52
|
+
try:
|
|
53
|
+
with Image.open(f) as img:
|
|
54
|
+
resized_img = img.resize(
|
|
55
|
+
(target_w, target_h), Image.Resampling.LANCZOS
|
|
56
|
+
)
|
|
57
|
+
resized_img.save(synced_name, format="BMP")
|
|
58
|
+
synced_files.append(synced_name)
|
|
59
|
+
created_synced_files.append(synced_name) # log the file path
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"Failed to resize {f}: {e}")
|
|
62
|
+
return []
|
|
63
|
+
else:
|
|
64
|
+
synced_files.append(f)
|
|
65
|
+
|
|
66
|
+
print(
|
|
67
|
+
f"Bending {len(synced_files)} BMP file(s) | Enc: {encoding.upper()} | XOR: {use_xor} | Echo: {delay_time}s"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# extract header from the first synced file
|
|
71
|
+
with open(synced_files[0], "rb") as f:
|
|
72
|
+
first_file_data = f.read()
|
|
73
|
+
|
|
74
|
+
pixel_offset = struct.unpack_from("<I", first_file_data, 10)[0]
|
|
75
|
+
header = bytearray(first_file_data[:pixel_offset])
|
|
76
|
+
|
|
77
|
+
target_byte_length = len(first_file_data) - pixel_offset
|
|
78
|
+
|
|
79
|
+
if encoding == "adpcm":
|
|
80
|
+
sample_multiplier = 2
|
|
81
|
+
else:
|
|
82
|
+
sample_multiplier = 1
|
|
83
|
+
|
|
84
|
+
target_sample_length = target_byte_length * sample_multiplier
|
|
85
|
+
|
|
86
|
+
# buffer for math
|
|
87
|
+
if use_xor:
|
|
88
|
+
mix_buffer = np.zeros(target_sample_length, dtype=np.int16)
|
|
89
|
+
else:
|
|
90
|
+
mix_buffer = np.zeros(target_sample_length, dtype=np.float32)
|
|
91
|
+
|
|
92
|
+
# where the real shit happens
|
|
93
|
+
for file in synced_files:
|
|
94
|
+
with open(file, "rb") as f:
|
|
95
|
+
data = f.read()
|
|
96
|
+
offset = struct.unpack_from("<I", data, 10)[0]
|
|
97
|
+
pixel_data = data[offset:]
|
|
98
|
+
|
|
99
|
+
if encoding == "ulaw":
|
|
100
|
+
pcm16_bytes = audioop.ulaw2lin(pixel_data, 2)
|
|
101
|
+
elif encoding == "adpcm":
|
|
102
|
+
pcm16_bytes, _ = audioop.adpcm2lin(pixel_data, 2, None)
|
|
103
|
+
else:
|
|
104
|
+
pcm16_bytes = audioop.alaw2lin(pixel_data, 2)
|
|
105
|
+
|
|
106
|
+
if use_xor:
|
|
107
|
+
audio_track = np.frombuffer(pcm16_bytes, dtype=np.int16)
|
|
108
|
+
else:
|
|
109
|
+
audio_track = np.frombuffer(pcm16_bytes, dtype=np.int16).astype(
|
|
110
|
+
np.float32
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if len(audio_track) > target_sample_length:
|
|
114
|
+
audio_track = audio_track[:target_sample_length]
|
|
115
|
+
elif len(audio_track) < target_sample_length:
|
|
116
|
+
if use_xor:
|
|
117
|
+
padded = np.zeros(target_sample_length, dtype=np.int16)
|
|
118
|
+
else:
|
|
119
|
+
padded = np.zeros(target_sample_length, dtype=np.float32)
|
|
120
|
+
padded[: len(audio_track)] = audio_track
|
|
121
|
+
audio_track = padded
|
|
122
|
+
|
|
123
|
+
if use_xor:
|
|
124
|
+
mix_buffer ^= audio_track
|
|
125
|
+
else:
|
|
126
|
+
mix_buffer += audio_track
|
|
127
|
+
|
|
128
|
+
# apply delay / echo
|
|
129
|
+
if delay_time > 0.0:
|
|
130
|
+
delay_samples = int(delay_time * 44100)
|
|
131
|
+
echo_track = np.roll(mix_buffer, delay_samples)
|
|
132
|
+
|
|
133
|
+
if use_xor:
|
|
134
|
+
mix_buffer ^= echo_track
|
|
135
|
+
else:
|
|
136
|
+
mix_buffer += echo_track * 0.5
|
|
137
|
+
|
|
138
|
+
# Prepare for export
|
|
139
|
+
if not use_xor:
|
|
140
|
+
mix_buffer = np.clip(mix_buffer, -32768.0, 32767.0)
|
|
141
|
+
mixed_pcm16_bytes = mix_buffer.astype(np.int16).tobytes()
|
|
142
|
+
else:
|
|
143
|
+
mixed_pcm16_bytes = mix_buffer.tobytes()
|
|
144
|
+
|
|
145
|
+
if encoding == "ulaw":
|
|
146
|
+
final_pixels = audioop.lin2ulaw(mixed_pcm16_bytes, 2)
|
|
147
|
+
elif encoding == "adpcm":
|
|
148
|
+
final_pixels, _ = audioop.lin2adpcm(mixed_pcm16_bytes, 2, None)
|
|
149
|
+
else:
|
|
150
|
+
final_pixels = audioop.lin2alaw(mixed_pcm16_bytes, 2)
|
|
151
|
+
|
|
152
|
+
# export
|
|
153
|
+
with open(output_file, "wb") as f:
|
|
154
|
+
f.write(header)
|
|
155
|
+
f.write(final_pixels[:target_byte_length])
|
|
156
|
+
|
|
157
|
+
print(f"Successfully bended: {output_file}")
|
|
158
|
+
|
|
159
|
+
# returns array of downscaled images to the caller app for deletion later
|
|
160
|
+
return created_synced_files
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# exec stuff
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
parser = argparse.ArgumentParser(
|
|
166
|
+
description="Databend BMP files using audio algorithms."
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument("images", nargs="+", help="Input BMP files")
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"-o",
|
|
171
|
+
"--output",
|
|
172
|
+
default="out/mash.bmp",
|
|
173
|
+
help="Output BMP file (default: out/mash.bmp)",
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument("-u", "--ulaw", action="store_true", help="Use u-law encoding")
|
|
176
|
+
parser.add_argument("-a", "--adpcm", action="store_true", help="Use ADPCM encoding")
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"-x",
|
|
179
|
+
"--xor",
|
|
180
|
+
action="store_true",
|
|
181
|
+
help="Use Bitwise XOR mixing instead of additive",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"-e", "--echo", action="store_true", help="Enable Delay/Echo effect"
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"-n",
|
|
188
|
+
"--delay_time",
|
|
189
|
+
type=float,
|
|
190
|
+
default=0.1,
|
|
191
|
+
help="Delay time in seconds for echo (default: 0.1)",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
args = parser.parse_args()
|
|
195
|
+
|
|
196
|
+
encoding = "alaw"
|
|
197
|
+
if args.adpcm:
|
|
198
|
+
encoding = "adpcm"
|
|
199
|
+
elif args.ulaw:
|
|
200
|
+
encoding = "ulaw"
|
|
201
|
+
|
|
202
|
+
delay_to_pass = args.delay_time if args.echo else 0.0
|
|
203
|
+
|
|
204
|
+
output_dir = os.path.dirname(args.output)
|
|
205
|
+
if output_dir:
|
|
206
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
207
|
+
|
|
208
|
+
databend(
|
|
209
|
+
args.images,
|
|
210
|
+
args.output,
|
|
211
|
+
encoding=encoding,
|
|
212
|
+
use_xor=args.xor,
|
|
213
|
+
delay_time=delay_to_pass,
|
|
214
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PIL import Image
|
|
4
|
+
from PySide6.QtWidgets import QFileDialog
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def browse_file():
|
|
8
|
+
input_file_path, _ = QFileDialog.getOpenFileNames(
|
|
9
|
+
None, caption="Select bmp files", filter="Images (*.png *.jpg *.jpeg *.bmp)"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
file_path = []
|
|
13
|
+
if input_file_path:
|
|
14
|
+
for paths in input_file_path:
|
|
15
|
+
imgbeforergb = Image.open(paths)
|
|
16
|
+
img = imgbeforergb.convert("RGB")
|
|
17
|
+
file_basename = os.path.basename(paths)
|
|
18
|
+
name, extension = os.path.splitext(file_basename)
|
|
19
|
+
bmp_filename = name + ".bmp"
|
|
20
|
+
|
|
21
|
+
output_path = os.path.join("/tmp", bmp_filename)
|
|
22
|
+
|
|
23
|
+
img.save(output_path)
|
|
24
|
+
file_path.append(output_path)
|
|
25
|
+
return file_path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def save_file():
|
|
29
|
+
save_path, _ = QFileDialog.getSaveFileName(
|
|
30
|
+
parent=None, caption="Save file:", filter="Bitmap Files (*.bmp)"
|
|
31
|
+
)
|
|
32
|
+
if save_path:
|
|
33
|
+
return save_path
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Quickbend
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: an app to create glitchart using pyside6
|
|
5
|
+
Author-email: Daniel Crutti <dancrutti@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: audioop-lts==0.2.2
|
|
12
|
+
Requires-Dist: numpy==2.4.6
|
|
13
|
+
Requires-Dist: pillow==12.2.0
|
|
14
|
+
Requires-Dist: PySide6==6.11.1
|
|
15
|
+
Requires-Dist: PySide6_Addons==6.11.1
|
|
16
|
+
Requires-Dist: PySide6_Essentials==6.11.1
|
|
17
|
+
Requires-Dist: shiboken6==6.11.1
|
|
18
|
+
|
|
19
|
+
# Quickbend
|
|
20
|
+
|
|
21
|
+
**Quickbend** is a tool for creating art via databending with images. It uses interpreting bmp files as logarithmic audio encoding schemes (a-law/u-law/adpcm) and then reconverting those back to bmp to create warped and glitched images.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/Quickbend/__init__.py
|
|
4
|
+
src/Quickbend/app.py
|
|
5
|
+
src/Quickbend/databend.py
|
|
6
|
+
src/Quickbend/files.py
|
|
7
|
+
src/Quickbend.egg-info/PKG-INFO
|
|
8
|
+
src/Quickbend.egg-info/SOURCES.txt
|
|
9
|
+
src/Quickbend.egg-info/dependency_links.txt
|
|
10
|
+
src/Quickbend.egg-info/entry_points.txt
|
|
11
|
+
src/Quickbend.egg-info/requires.txt
|
|
12
|
+
src/Quickbend.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Quickbend
|