Skip to content

Instantly share code, notes, and snippets.

@kalomaze
Last active January 8, 2025 01:54
Show Gist options
  • Save kalomaze/52d111a196456126b20ed97daabf24db to your computer and use it in GitHub Desktop.
Save kalomaze/52d111a196456126b20ed97daabf24db to your computer and use it in GitHub Desktop.
import sys
import random
import numpy as np
import string
from datetime import datetime
from PIL import Image, ImageEnhance, ImageOps
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QTextEdit, QPushButton, QCheckBox,
QLabel, QSpinBox, QComboBox, QSlider, QFileDialog,
QFrame)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QImage, QFont
import pyqtgraph as pg
class ASCIICorruptor(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("ASCII Art Generator & Corruptor")
self.setGeometry(100, 100, 1400, 800)
# ASCII characters for different brightness levels
self.ASCII_CHARS = "@%#*+=-:. "
self.current_image = None
self.current_ascii = None
self.batch_images = []
self.batch_ascii = [] # Add this line to initialize batch_ascii
self.init_ui()
def init_ui(self):
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QHBoxLayout(main_widget)
# Left panel - Image and ASCII preview
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
# Image preview
self.image_label = QLabel("Click to select image")
self.image_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
self.image_label.setMinimumSize(300, 300)
self.image_label.setAlignment(Qt.AlignCenter)
self.image_label.mousePressEvent = self.load_image
# ASCII preview
self.ascii_preview = QTextEdit()
self.ascii_preview.setFont(QFont("Courier", 8))
self.ascii_preview.setReadOnly(True)
left_layout.addWidget(self.image_label)
left_layout.addWidget(self.ascii_preview)
# Middle panel - Controls
middle_panel = QWidget()
middle_layout = QVBoxLayout(middle_panel)
# Image controls
image_controls = QWidget()
image_layout = QVBoxLayout(image_controls)
# Sliders
self.sliders = {}
slider_configs = {
'Max Characters': (10, 200, 100),
'Brightness': (0, 200, 100),
'Contrast': (0, 200, 100),
'Saturation': (0, 200, 100),
}
for name, (min_val, max_val, default) in slider_configs.items():
slider_widget = QWidget()
slider_layout = QHBoxLayout(slider_widget)
slider_layout.addWidget(QLabel(f"{name}:"))
slider = QSlider(Qt.Horizontal)
slider.setMinimum(min_val)
slider.setMaximum(max_val)
slider.setValue(default)
slider.valueChanged.connect(self.update_ascii)
self.sliders[name] = slider
slider_layout.addWidget(slider)
image_layout.addWidget(slider_widget)
# Corruption controls
corruption_controls = QWidget()
corruption_layout = QVBoxLayout(corruption_controls)
# Corruption pattern selector
self.pattern_type = QComboBox()
self.pattern_type.addItems(['Constant', 'Gaussian', 'Linear Descent'])
corruption_layout.addWidget(QLabel("Corruption Pattern:"))
corruption_layout.addWidget(self.pattern_type)
# Growth curve selector
self.curve_type = QComboBox()
self.curve_type.addItems(['Linear', 'Exponential', 'Logarithmic', 'Sigmoid', 'Square Root'])
corruption_layout.addWidget(QLabel("Growth Curve:"))
corruption_layout.addWidget(self.curve_type)
# Number of corruption steps
steps_widget = QWidget()
steps_layout = QHBoxLayout(steps_widget)
steps_layout.addWidget(QLabel("Corruption Steps:"))
self.num_steps = QSpinBox()
self.num_steps.setValue(8)
self.num_steps.setRange(2, 64)
steps_layout.addWidget(self.num_steps)
corruption_layout.addWidget(steps_widget)
# Add scaling controls
scaling_widget = QWidget()
scaling_layout = QHBoxLayout(scaling_widget)
self.enable_scaling = QCheckBox("Enable Size Scaling")
scaling_layout.addWidget(self.enable_scaling)
self.scale_factor = QSpinBox()
self.scale_factor.setRange(110, 200)
self.scale_factor.setValue(150)
self.scale_factor.setSuffix("%")
scaling_layout.addWidget(QLabel("Scale Factor per Step:"))
scaling_layout.addWidget(self.scale_factor)
corruption_layout.addWidget(scaling_widget)
# Buttons
buttons_widget = QWidget()
buttons_layout = QHBoxLayout(buttons_widget)
self.generate_btn = QPushButton("Generate ASCII")
self.corrupt_btn = QPushButton("Corrupt")
self.save_btn = QPushButton("Save Result")
self.generate_btn.clicked.connect(self.update_ascii)
self.corrupt_btn.clicked.connect(self.corrupt_ascii)
self.save_btn.clicked.connect(self.save_result)
buttons_layout.addWidget(self.generate_btn)
buttons_layout.addWidget(self.corrupt_btn)
buttons_layout.addWidget(self.save_btn)
# Add a new button for batch import
self.batch_import_btn = QPushButton("Batch Import")
self.batch_import_btn.clicked.connect(self.batch_import_images)
buttons_layout.addWidget(self.batch_import_btn)
# Add all controls to middle panel
middle_layout.addWidget(image_controls)
middle_layout.addWidget(corruption_controls)
middle_layout.addWidget(buttons_widget)
middle_layout.addStretch()
# Right panel - Corruption preview and graph
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
self.corruption_preview = QTextEdit()
self.corruption_preview.setFont(QFont("Courier", 8))
self.plot_widget = pg.PlotWidget()
self.plot_widget.setBackground('w')
self.plot_widget.showGrid(x=True, y=True)
right_layout.addWidget(self.corruption_preview)
right_layout.addWidget(self.plot_widget)
# Add panels to main layout
layout.addWidget(left_panel)
layout.addWidget(middle_panel)
layout.addWidget(right_panel)
# Store corrupted versions
self.corrupted_versions = []
def load_image(self, event):
file_name, _ = QFileDialog.getOpenFileName(self, "Select Image", "",
"Image Files (*.png *.jpg *.jpeg *.bmp)")
if file_name:
self.current_image = Image.open(file_name)
# Display preview
pixmap = QPixmap(file_name)
scaled_pixmap = pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(scaled_pixmap)
self.update_ascii()
def image_to_ascii(self, image, width_override=None):
# Convert image to grayscale
image = image.convert('L')
# Apply image adjustments based on sliders
brightness = ImageEnhance.Brightness(image).enhance(self.sliders['Brightness'].value() / 100)
contrast = ImageEnhance.Contrast(brightness).enhance(self.sliders['Contrast'].value() / 100)
# Resize image with minimum size protection
width = max(2, width_override if width_override is not None else self.sliders['Max Characters'].value())
aspect_ratio = image.size[1] / image.size[0]
height = max(2, int(width * aspect_ratio * 0.5)) # Multiply by 0.5 to account for character aspect ratio
image = contrast.resize((width, height))
# Convert pixels to ASCII
pixels = np.array(image)
ascii_str = ""
char_length = len(self.ASCII_CHARS) - 1
for row in pixels:
for pixel_val in row:
char_index = int((pixel_val / 255.0) * char_length)
ascii_str += self.ASCII_CHARS[char_index]
ascii_str += "\n"
return ascii_str
def update_ascii(self):
if self.current_image:
self.current_ascii = self.image_to_ascii(self.current_image)
self.ascii_preview.setText(self.current_ascii)
def generate_pattern(self, length, noise_level):
x = np.linspace(0, 1, length)
pattern_type = self.pattern_type.currentText()
max_val = noise_level
if pattern_type == 'Constant':
pattern = np.full(length, max_val)
elif pattern_type == 'Gaussian':
mean = random.uniform(0.3, 0.7)
std = random.uniform(0.1, 0.3)
pattern = max_val * np.exp(-((x - mean) ** 2) / (2 * std ** 2))
else: # Linear Descent
pattern = max_val * (1 - x)
return pattern
def corrupt_ascii(self):
if not self.current_ascii and not self.batch_ascii:
return
steps = self.num_steps.value()
self.corrupted_versions = []
images_to_process = self.batch_images if self.batch_ascii else [self.current_image]
for image in images_to_process:
image_corrupted_versions = []
max_width = self.sliders['Max Characters'].value()
# Generate corrupted versions for this image
for step in range(steps):
noise_level = 100 * (steps - step - 1) / (steps - 1)
# Calculate scaled width if scaling is enabled
if self.enable_scaling.isChecked():
progress = step / (steps - 1) # Goes from 0 to 1
scale_factor = self.scale_factor.value() / 100.0
# Apply different growth curves
curve_type = self.curve_type.currentText()
if curve_type == 'Linear':
scaled_progress = progress
elif curve_type == 'Exponential':
scaled_progress = np.exp(3 * progress - 3) # Scaled to [0,1]
elif curve_type == 'Logarithmic':
scaled_progress = np.log(1 + 9 * progress) / np.log(10) # log10(1 + 9x)
elif curve_type == 'Sigmoid':
scaled_progress = 1 / (1 + np.exp(-10 * (progress - 0.5))) # Centered sigmoid
else: # Square Root
scaled_progress = np.sqrt(progress)
# Scale factor now modifies the intensity of the chosen curve
final_progress = scaled_progress ** scale_factor
current_width = int(max_width * final_progress)
else:
current_width = max_width
# Generate ASCII art at current scale
current_ascii = self.image_to_ascii(image, current_width)
# Apply corruption pattern
pattern = self.generate_pattern(len(current_ascii), noise_level)
# Apply corruption
result = ""
result += "```\n" # Add separator before each step
for i, char in enumerate(current_ascii):
if char == '\n':
result += char
continue
if random.random() * 100 < pattern[i]:
result += random.choice(self.ASCII_CHARS)
else:
result += char
result += "\n```\n" # Add separator after each step
image_corrupted_versions.append(result)
self.corrupted_versions.append(image_corrupted_versions)
# Update corruption preview with the last processed image
full_output = ""
for i, version in enumerate(self.corrupted_versions[-1]):
full_output += f"Step {i + 1}:\n{version}\n\n"
self.corruption_preview.setText(full_output)
def batch_import_images(self):
file_names, _ = QFileDialog.getOpenFileNames(self, "Select Images", "",
"Image Files (*.png *.jpg *.jpeg *.bmp)")
if file_names:
self.batch_images = [Image.open(file_name) for file_name in file_names]
self.batch_ascii = []
for image in self.batch_images:
ascii_art = self.image_to_ascii(image)
self.batch_ascii.append(ascii_art)
# Update preview with the first image
self.current_image = self.batch_images[0]
pixmap = QPixmap(file_names[0])
scaled_pixmap = pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(scaled_pixmap) # Fixed typo here
self.update_ascii()
def save_result(self):
if not self.corrupted_versions:
return
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename, _ = QFileDialog.getSaveFileName(self, "Save Result",
f"ascii_art_{timestamp}.txt",
"Text Files (*.txt)")
if filename:
with open(filename, 'w', encoding='utf-8') as f:
for img_index, image_versions in enumerate(self.corrupted_versions, 1):
f.write(f"# IMAGE {img_index}\n\n")
for step, version in enumerate(image_versions, 1):
f.write(f"Step {step}:\n{version}\n\n")
f.write("=" * 50 + "\n\n")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = ASCIICorruptor()
window.show()
sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment