From abe8f1a59f459ed214679f0fb8d5d5a3dad3679b Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:47:08 +0330 Subject: [PATCH 1/6] Delete main.py --- main.py | 56 -------------------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 main.py diff --git a/main.py b/main.py deleted file mode 100644 index 8e9c773..0000000 --- a/main.py +++ /dev/null @@ -1,56 +0,0 @@ -import sys -import os -import traceback -from PyQt6.QtWidgets import QApplication -from ui import PixelSortApp -from ui.logger import setup_logger, get_logger - -def log_environment_info(logger): - """Log relevant environment information for debugging.""" - logger.debug(f"Display: {os.environ.get('DISPLAY')}") - logger.debug(f"Wayland Display: {os.environ.get('WAYLAND_DISPLAY')}") - logger.debug(f"Qt Platform: {os.environ.get('QT_QPA_PLATFORM')}") - -def initialize_application(logger): - """Initialize and return the QApplication instance.""" - app = QApplication(sys.argv) - logger.info("QApplication created") - logger.debug(f"Available platforms: {QApplication.platformName()}") - return app - -def create_main_window(logger): - """Create and return the main application window.""" - window = PixelSortApp() - logger.info("Window created") - return window - -def handle_error(logger, error): - """Handle application errors and log them appropriately.""" - logger.error(f"Error: {str(error)}") - logger.error("Traceback:", exc_info=True) - sys.exit(1) - -def run_application(): - """Main application runner function.""" - logger = setup_logger() - app_logger = get_logger('main') - - try: - app_logger.info("Starting application...") - log_environment_info(app_logger) - - app = initialize_application(app_logger) - window = create_main_window(app_logger) - - window.show() - app_logger.info("Window shown") - exit_code = app.exec() - app_logger.info("Application exited successfully") - sys.exit(exit_code) - except Exception as e: - handle_error(app_logger, e) - -if __name__ == "__main__": - run_application() - -# coast was here :3 From e88a5febec6ff970c1e3d8521baf017ea323a00d Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:47:33 +0330 Subject: [PATCH 2/6] Delete ui directory --- ui/__init__.py | 4 - ui/__pycache__/__init__.cpython-313.pyc | Bin 312 -> 0 bytes ui/__pycache__/pixel_sort_app.cpython-313.pyc | Bin 19108 -> 0 bytes ui/__pycache__/worker.cpython-313.pyc | Bin 14454 -> 0 bytes ui/logger.py | 49 --- ui/main_window.ui | 302 ----------------- ui/pixel_sort_app.py | 307 ----------------- ui/worker.py | 313 ------------------ 8 files changed, 975 deletions(-) delete mode 100644 ui/__init__.py delete mode 100644 ui/__pycache__/__init__.cpython-313.pyc delete mode 100644 ui/__pycache__/pixel_sort_app.cpython-313.pyc delete mode 100644 ui/__pycache__/worker.cpython-313.pyc delete mode 100644 ui/logger.py delete mode 100644 ui/main_window.ui delete mode 100644 ui/pixel_sort_app.py delete mode 100644 ui/worker.py diff --git a/ui/__init__.py b/ui/__init__.py deleted file mode 100644 index 0eedc11..0000000 --- a/ui/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .pixel_sort_app import PixelSortApp -from .worker import PixelSortWorker - -__all__ = ['PixelSortApp', 'PixelSortWorker'] \ No newline at end of file diff --git a/ui/__pycache__/__init__.cpython-313.pyc b/ui/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 67d471c50c9fb48b133b138961f9cc9b0c49a88e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 zcmey&%ge<81i^jQ88txqF^B^LOi;#W6(D0OLoh=yqc?*WV-ceQh|T29`lZFG zMfypJRaKcKsqty4DfvZ-`WgATsrtp0#fd4onR)sFMfq8&$tA`5KzrOule6_pGxg)+ zGxIV_;^XxSDsOSv0GkL^MgRZ+ diff --git a/ui/__pycache__/pixel_sort_app.cpython-313.pyc b/ui/__pycache__/pixel_sort_app.cpython-313.pyc deleted file mode 100644 index ed98c0ced7fefef89ecd85896926dd1a91885134..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19108 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 OZ+i5%aHWFFi}ZhrfBJO* diff --git a/ui/logger.py b/ui/logger.py deleted file mode 100644 index efab41c..0000000 --- a/ui/logger.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import os -from datetime import datetime - -def setup_logger(): - # Create logs directory if it doesn't exist - if not os.path.exists('logs'): - os.makedirs('logs') - - # Configure logging - log_file = os.path.join('logs', 'debug.log') - - # Clear previous log file if it exists - if os.path.exists(log_file): - try: - with open(log_file, 'w') as f: - f.write(f"=== New Session Started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===\n") - except Exception as e: - print(f"Warning: Could not clear previous log file: {e}") - - # Create formatter - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - - # Setup file handler - file_handler = logging.FileHandler(log_file) - file_handler.setLevel(logging.DEBUG) - file_handler.setFormatter(formatter) - - # Setup console handler - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - console_handler.setFormatter(formatter) - - # Get root logger - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - - # Add handlers - logger.addHandler(file_handler) - logger.addHandler(console_handler) - - return logger - -# Create a function to get a logger for a specific module -def get_logger(name): - return logging.getLogger(name) \ No newline at end of file diff --git a/ui/main_window.ui b/ui/main_window.ui deleted file mode 100644 index 18c354d..0000000 --- a/ui/main_window.ui +++ /dev/null @@ -1,302 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1400 - 740 - - - - Enhanced Pixel Sorting App - - - - - - - - 16777215 - 80 - - - - File Operations - - - - - - Load Image - - - - - - - false - - - Save Sorted Image - - - - - - - - - - - 16777215 - 140 - - - - Sorting Options - - - - - - Sort Criterion: - - - - - - - - Brightness - - - - - Hue - - - - - Saturation - - - - - Intensity - - - - - Minimum - - - - - - - - Pattern: - - - - - - - - Linear - - - - - Radial - - - - - Spiral - - - - - Wave - - - - - - - - Sort Angle: - - - - - - - 0 - - - 359 - - - 0 - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBelow - - - 30 - - - - - - - ° - - - 0 - - - 359 - - - 0 - - - - - - - Intensity: - - - - - - - 1 - - - 100 - - - 100 - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBelow - - - 10 - - - - - - - % - - - 1 - - - 100 - - - 100 - - - - - - - - - - - 16777215 - 100 - - - - Sort Control - - - - - - false - - - Sort Pixels - - - - - - - 0 - - - - - - - - - - - - - - Original Image, Load an image with the button above - - - Qt::AlignmentFlag::AlignCenter - - - - - - - - - - - Sorted Image will Render Here upon completion of Sorting. - - - Qt::AlignmentFlag::AlignCenter - - - - - - - - - - - - - 0 - 0 - 1400 - 23 - - - - - - - - diff --git a/ui/pixel_sort_app.py b/ui/pixel_sort_app.py deleted file mode 100644 index 8cb0591..0000000 --- a/ui/pixel_sort_app.py +++ /dev/null @@ -1,307 +0,0 @@ -import os -import psutil -from PyQt6.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QLabel, QSpinBox, QProgressDialog -from PyQt6.QtGui import QPixmap, QImage -from PyQt6.QtCore import Qt -from PyQt6 import uic -from PIL import Image, UnidentifiedImageError -from .worker import PixelSortWorker -from .logger import get_logger - -class PixelSortApp(QMainWindow): - def __init__(self): - super().__init__() - self.logger = get_logger('PixelSortApp') - self.logger.info("Initializing PixelSortApp") - - # Load the UI file - uic.loadUi('ui/main_window.ui', self) - - # Initialize variables to hold images - self.original_image = None - self.sorted_image = None - - # Initialize worker thread - self.worker = None - - # Set up image labels - self.original_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.sorted_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.original_label.setMinimumSize(400, 300) - self.sorted_label.setMinimumSize(400, 300) - - # Connect signals - self.setup_connections() - self.logger.info("PixelSortApp initialization complete") - - def setup_connections(self): - self.logger.debug("Setting up signal connections") - self.load_button.clicked.connect(self.load_image) - self.save_button.clicked.connect(self.save_image) - self.sort_button.clicked.connect(self.sort_pixels) - self.angle_slider.valueChanged.connect(self.update_angle_spinbox) - self.intensity_slider.valueChanged.connect(self.update_intensity_spinbox) - self.angle_value_label.valueChanged.connect(self.update_angle_slider) - self.intensity_value_label.valueChanged.connect(self.update_intensity_slider) - - def update_intensity_spinbox(self, value): - self.logger.debug(f"Updating intensity spinbox to {value}%") - self.intensity_value_label.setValue(value) - - def update_angle_spinbox(self, value): - self.logger.debug(f"Updating angle spinbox to {value}°") - self.angle_value_label.setValue(value) - - def update_intensity_slider(self, value): - self.logger.debug(f"Updating intensity slider to {value}%") - self.intensity_slider.setValue(value) - - def update_angle_slider(self, value): - self.logger.debug(f"Updating angle slider to {value}°") - self.angle_slider.setValue(value) - - def validate_image_size(self, image_path): - """Validate if the image size is within acceptable limits.""" - try: - with Image.open(image_path) as img: - width, height = img.size - # Calculate approximate memory usage (3 bytes per pixel for RGB) - memory_usage = width * height * 3 - # Get available system memory - available_memory = psutil.virtual_memory().available - - # Check if image is too large (more than 50% of available memory) - if memory_usage > available_memory * 0.5: - return False, f"Image is too large ({width}x{height}). Please use a smaller image." - - # Check if dimensions are too large - if width > 10000 or height > 10000: - return False, f"Image dimensions ({width}x{height}) exceed maximum allowed size (10000x10000)." - - return True, None - except Exception as e: - return False, f"Error validating image: {str(e)}" - - def load_image(self): - """Load an image with enhanced error handling and validation.""" - self.logger.info("Opening file dialog to load image") - options = QFileDialog.Option.DontUseNativeDialog - file_name, _ = QFileDialog.getOpenFileName( - self, "Open Image File", "", - "Images (*.png *.xpm *.jpg *.jpeg *.bmp *.gif);;All Files (*)", - options=options - ) - - if not file_name: - return - - try: - # Validate file exists and is readable - if not os.path.isfile(file_name): - raise FileNotFoundError(f"File not found: {file_name}") - - if not os.access(file_name, os.R_OK): - raise PermissionError(f"Cannot read file: {file_name}") - - # Validate image size and memory requirements - is_valid, error_msg = self.validate_image_size(file_name) - if not is_valid: - QMessageBox.critical(self, "Error", error_msg) - return - - # Show loading progress dialog - progress = QProgressDialog("Loading image...", None, 0, 100, self) - progress.setWindowModality(Qt.WindowModality.WindowModal) - progress.setWindowTitle("Loading") - progress.setMinimumDuration(0) - progress.setValue(0) - progress.show() - - self.logger.info(f"Loading image from: {file_name}") - - # Load image with progress updates - with Image.open(file_name) as img: - # Convert to RGB if necessary - if img.mode != 'RGB': - image = img.convert('RGB') - else: - image = img.copy() - progress.setValue(30) - - # Store the original image - self.original_image = image - progress.setValue(60) - - # Display the image - self.display_image(image, self.original_label) - progress.setValue(90) - - # Reset UI state - self.sorted_label.clear() - self.sorted_label.setText("Sorted Image") - self.sort_button.setEnabled(True) - self.save_button.setEnabled(False) - self.progress_bar.setValue(0) - - progress.setValue(100) - self.logger.info("Image loaded successfully") - - except UnidentifiedImageError: - self.logger.error(f"Invalid image format: {file_name}") - QMessageBox.critical(self, "Error", "The selected file is not a valid image format.") - except FileNotFoundError as e: - self.logger.error(str(e)) - QMessageBox.critical(self, "Error", str(e)) - except PermissionError as e: - self.logger.error(str(e)) - QMessageBox.critical(self, "Error", str(e)) - except MemoryError: - self.logger.error("Not enough memory to load the image") - QMessageBox.critical(self, "Error", "Not enough memory to load the image. Please try a smaller image.") - except Exception as e: - self.logger.error(f"Failed to load image: {str(e)}", exc_info=True) - QMessageBox.critical(self, "Error", f"Failed to load image:\n{str(e)}") - finally: - if 'progress' in locals(): - progress.close() - - def display_image(self, image, label): - """Display an image in a label while maintaining aspect ratio.""" - self.logger.debug(f"Displaying image of size {image.size}") - try: - # Convert PIL Image to QImage - qimage = self.pil_to_qimage(image) - if qimage.isNull(): - self.logger.error("Failed to convert PIL image to QImage") - return - - # Create pixmap from QImage - pixmap = QPixmap.fromImage(qimage) - if pixmap.isNull(): - self.logger.error("Failed to create QPixmap from QImage") - return - - # Get label size - label_size = label.size() - - # Calculate scaling factors - w_scale = label_size.width() / pixmap.width() - h_scale = label_size.height() / pixmap.height() - - # Use the smaller scale to maintain aspect ratio - scale = min(w_scale, h_scale) - - # Calculate new dimensions - new_width = int(pixmap.width() * scale) - new_height = int(pixmap.height() * scale) - - # Scale the pixmap - scaled_pixmap = pixmap.scaled( - new_width, - new_height, - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation - ) - - # Center the pixmap in the label - label.setAlignment(Qt.AlignmentFlag.AlignCenter) - label.setPixmap(scaled_pixmap) - - except Exception as e: - self.logger.error(f"Error displaying image: {str(e)}", exc_info=True) - - def pil_to_qimage(self, image): - """Convert PIL Image to QImage with proper format handling.""" - self.logger.debug("Converting PIL image to QImage") - try: - # Ensure image is in RGB mode - if image.mode != 'RGB': - image = image.convert('RGB') - - # Get image data - data = image.tobytes("raw", "RGB") - - # Create QImage with proper format - qimage = QImage( - data, - image.width, - image.height, - image.width * 3, # stride - QImage.Format.Format_RGB888 - ) - - # Create a deep copy to ensure data ownership - return qimage.copy() - - except Exception as e: - self.logger.error(f"Error converting PIL image to QImage: {str(e)}", exc_info=True) - return QImage() - - def resizeEvent(self, event): - self.logger.debug("Window resize event triggered") - # Refresh images on window resize - if self.original_image: - self.display_image(self.original_image, self.original_label) - if self.sorted_image: - self.display_image(self.sorted_image, self.sorted_label) - super().resizeEvent(event) - - def sort_pixels(self): - if self.original_image is None: - self.logger.warning("Attempted to sort pixels without loading an image") - QMessageBox.warning(self, "Warning", "No image loaded to sort.") - return - - self.logger.info("Starting pixel sorting operation") - # Disable buttons to prevent multiple operations - self.sort_button.setEnabled(False) - self.save_button.setEnabled(False) - - # Get sorting parameters - angle = self.angle_slider.value() - criterion = self.criteria_combo.currentText() - pattern = self.pattern_combo.currentText() - intensity = self.intensity_slider.value() / 100.0 - - # Start the worker thread - self.worker = PixelSortWorker(self.original_image, angle, criterion, pattern, intensity) - self.worker.progress.connect(self.update_progress) - self.worker.finished.connect(self.on_sort_finished) - self.worker.error.connect(self.on_sort_error) - self.worker.start() - - def update_progress(self, value): - self.progress_bar.setValue(value) - - def on_sort_finished(self, sorted_image): - self.logger.info("Sorting finished, displaying result") - self.sorted_image = sorted_image - self.display_image(sorted_image, self.sorted_label) - self.sort_button.setEnabled(True) - self.save_button.setEnabled(True) - self.worker = None - - def on_sort_error(self, error_message): - self.logger.error(f"Sorting error: {error_message}") - QMessageBox.critical(self, "Error", f"An error occurred during sorting:\n{error_message}") - self.sort_button.setEnabled(True) - self.worker = None - - def save_image(self): - if self.sorted_image is None: - QMessageBox.warning(self, "Warning", "No sorted image to save.") - return - options = QFileDialog.Option.DontUseNativeDialog - file_name, _ = QFileDialog.getSaveFileName( - self, "Save Image File", "", - "PNG Image (*.png);;JPEG Image (*.jpg);;BMP Image (*.bmp);;All Files (*)", - options=options - ) - if file_name: - try: - self.logger.info(f"Saving image to: {file_name}") - self.sorted_image.save(file_name) - self.logger.info("Image saved successfully") - except Exception as e: - self.logger.error(f"Failed to save image: {str(e)}", exc_info=True) - QMessageBox.critical(self, "Error", f"Failed to save image:\n{e}") \ No newline at end of file diff --git a/ui/worker.py b/ui/worker.py deleted file mode 100644 index 833dc58..0000000 --- a/ui/worker.py +++ /dev/null @@ -1,313 +0,0 @@ -import traceback -from PyQt6.QtCore import QThread, pyqtSignal -from PIL import Image -import numpy as np -from numba import njit -import scipy.ndimage -from .logger import get_logger - -logger = get_logger('PixelSortWorker') - -# Numba-compatible RGB to HSV conversion -@njit -def rgb_to_hsv_numba(r, g, b): - maxc = np.maximum(np.maximum(r, g), b) - minc = np.minimum(np.minimum(r, g), b) - delta = maxc - minc - h = np.zeros_like(maxc, dtype=np.float32) - s = np.zeros_like(maxc, dtype=np.float32) - v = maxc.astype(np.float32) - - mask = delta != 0 - s[mask] = delta[mask] / maxc[mask] - - rc = np.zeros_like(maxc, dtype=np.float32) - gc = np.zeros_like(maxc, dtype=np.float32) - bc = np.zeros_like(maxc, dtype=np.float32) - rc[mask] = (maxc[mask] - r[mask]) / delta[mask] - gc[mask] = (maxc[mask] - g[mask]) / delta[mask] - bc[mask] = (maxc[mask] - b[mask]) / delta[mask] - - cond_r = (r == maxc) & mask - cond_g = (g == maxc) & mask - cond_b = (b == maxc) & mask - - h[cond_r] = bc[cond_r] - gc[cond_r] - h[cond_g] = np.float32(2.0) + rc[cond_g] - bc[cond_g] - h[cond_b] = np.float32(4.0) + gc[cond_b] - rc[cond_b] - - h = (h / np.float32(6.0)) % np.float32(1.0) - h[~mask] = np.float32(0.0) - - return h, s, v - -@njit -def row_max(arr): - n_rows, n_cols = arr.shape - result = np.empty(n_rows, dtype=arr.dtype) - for i in range(n_rows): - max_val = arr[i, 0] - for j in range(1, n_cols): - if arr[i, j] > max_val: - max_val = arr[i, j] - result[i] = max_val - return result - -@njit -def row_min(arr): - n_rows, n_cols = arr.shape - result = np.empty(n_rows, dtype=arr.dtype) - for i in range(n_rows): - min_val = arr[i, 0] - for j in range(1, n_cols): - if arr[i, j] < min_val: - min_val = arr[i, j] - result[i] = min_val - return result - -@njit -def process_line(line, criterion_id): - length = line.shape[0] - key = np.empty(length, dtype=np.float32) - line = line.astype(np.float32) - if criterion_id == 0: - for j in range(length): - key[j] = (line[j, 0] + line[j, 1] + line[j, 2]) / np.float32(3.0) - elif criterion_id == 1: - r = line[:, 0] / np.float32(255.0) - g = line[:, 1] / np.float32(255.0) - b = line[:, 2] / np.float32(255.0) - h, s, v = rgb_to_hsv_numba(r, g, b) - key = h.astype(np.float32) - elif criterion_id == 2: - r = line[:, 0] / np.float32(255.0) - g = line[:, 1] / np.float32(255.0) - b = line[:, 2] / np.float32(255.0) - h, s, v = rgb_to_hsv_numba(r, g, b) - key = s.astype(np.float32) - elif criterion_id == 3: - max_vals = row_max(line) - min_vals = row_min(line) - key = (max_vals + min_vals) / np.float32(2.0) - elif criterion_id == 4: - key = row_min(line) - else: - for j in range(length): - key[j] = (line[j, 0] + line[j, 1] + line[j, 2]) / np.float32(3.0) - - sorted_indices = np.argsort(key) - sorted_line = line[sorted_indices].astype(np.uint8) - return sorted_line - -class PixelSortWorker(QThread): - progress = pyqtSignal(int) - finished = pyqtSignal(Image.Image) - error = pyqtSignal(str) - - def __init__(self, image, angle, criterion, pattern, intensity): - super().__init__() - self.logger = get_logger('PixelSortWorker') - self.image = image - self.angle = angle - self.criterion = criterion - self.pattern = pattern - self.intensity = intensity - self.logger.info(f"Initialized worker with angle={angle}, criterion={criterion}, pattern={pattern}, intensity={intensity}") - - def run(self): - try: - self.logger.info("Starting pixel sorting operation") - sorted_image = self.pixel_sort(self.image, self.angle, self.criterion, self.pattern, self.intensity) - self.logger.info("Pixel sorting completed successfully") - self.finished.emit(sorted_image) - except Exception as e: - self.logger.error(f"Error during pixel sorting: {str(e)}", exc_info=True) - tb = traceback.format_exc() - self.error.emit(f"{str(e)}\n{tb}") - - def pixel_sort(self, image, angle, criterion, pattern, intensity): - self.logger.debug(f"Starting pixel_sort with image size {image.size}") - # Convert image to NumPy array - img_array = np.array(image) - height, width, channels = img_array.shape - self.logger.debug(f"Original image shape: {img_array.shape}") - - # Map criterion to integer id - criterion_map = { - 'Brightness': 0, - 'Hue': 1, - 'Saturation': 2, - 'Intensity': 3, - 'Minimum': 4, - } - criterion_id = criterion_map.get(criterion, 0) - self.logger.debug(f"Using criterion ID: {criterion_id} ({criterion})") - self.logger.debug(f"Using pattern: {pattern}") - - # Convert angle to radians - angle_rad = np.radians(angle) - - # Normalize angle to -180 to 180 degrees - angle_rad = angle_rad % (2 * np.pi) - if angle_rad > np.pi: - angle_rad -= 2 * np.pi - - # Determine if we should shear horizontally or vertically - # For angles between -45 and 45 degrees, shear horizontally - # For angles between 45 and 135 degrees, shear vertically - if -np.pi/4 <= angle_rad <= np.pi/4: - # Shear horizontally - shear_factor = np.tan(angle_rad) - shear_matrix = np.array([ - [1, shear_factor, 0], - [0, 1, 0], - [0, 0, 1] - ]) - inverse_shear_matrix = np.array([ - [1, -shear_factor, 0], - [0, 1, 0], - [0, 0, 1] - ]) - # Sort horizontally - sort_axis = 1 - else: - # Shear vertically - shear_factor = 1.0 / np.tan(angle_rad) - shear_matrix = np.array([ - [1, 0, 0], - [shear_factor, 1, 0], - [0, 0, 1] - ]) - inverse_shear_matrix = np.array([ - [1, 0, 0], - [-shear_factor, 1, 0], - [0, 0, 1] - ]) - # Sort vertically - sort_axis = 0 - - # Apply shear transformation - self.logger.debug("Applying shear transformation") - sheared_array = scipy.ndimage.affine_transform( - img_array, - shear_matrix, - offset=[0, 0, 0], - order=1, - mode='wrap', - cval=0 - ) - - # Sort pixels - self.logger.debug("Sorting pixels") - sorted_array = sheared_array.copy() - - # Process each line along the appropriate axis - if sort_axis == 1: # Sort horizontally - for i in range(sheared_array.shape[0]): - line = sorted_array[i, :, :].copy() - sorted_line = process_line(line, criterion_id) - if intensity < 1.0: - mask = np.random.rand(line.shape[0]) < intensity - blended_line = line.copy() - blended_line[mask] = sorted_line[mask] - sorted_array[i, :, :] = blended_line - else: - sorted_array[i, :, :] = sorted_line - progress_percent = int(((i + 1) / sheared_array.shape[0]) * 100) - self.progress.emit(progress_percent) - else: # Sort vertically - for i in range(sheared_array.shape[1]): - line = sorted_array[:, i, :].copy() - sorted_line = process_line(line, criterion_id) - if intensity < 1.0: - mask = np.random.rand(line.shape[0]) < intensity - blended_line = line.copy() - blended_line[mask] = sorted_line[mask] - sorted_array[:, i, :] = blended_line - else: - sorted_array[:, i, :] = sorted_line - progress_percent = int(((i + 1) / sheared_array.shape[1]) * 100) - self.progress.emit(progress_percent) - - # Apply inverse shear transformation - self.logger.debug("Applying inverse shear transformation") - result_array = scipy.ndimage.affine_transform( - sorted_array, - inverse_shear_matrix, - offset=[0, 0, 0], - order=1, - mode='wrap', - cval=0 - ) - - # Ensure the result has the same shape as the input - if result_array.shape != img_array.shape: - result_array = self.maintain_aspect_ratio(result_array, img_array.shape) - - # Convert back to PIL Image - sorted_image = Image.fromarray(result_array.astype(np.uint8)) - self.logger.debug("Pixel sorting completed") - return sorted_image - - def get_line_points(self, x1, y1, x2, y2): - """Get all points along a line using Bresenham's line algorithm.""" - points = [] - dx = abs(x2 - x1) - dy = abs(y2 - y1) - x, y = x1, y1 - n = 1 + dx + dy - x_inc = 1 if x2 > x1 else -1 - y_inc = 1 if y2 > y1 else -1 - error = dx - dy - dx *= 2 - dy *= 2 - - for _ in range(n): - points.append((x, y)) - if error > 0: - x += x_inc - error -= dy - else: - y += y_inc - error += dx - - return points - - def maintain_aspect_ratio(self, img_array, target_shape): - """Maintain aspect ratio while resizing the image to match target shape.""" - self.logger.debug(f"Maintaining aspect ratio: current shape {img_array.shape} -> target shape {target_shape}") - - current_height, current_width = img_array.shape[:2] - target_height, target_width = target_shape[:2] - - # Calculate scaling factors - scale_h = target_height / current_height - scale_w = target_width / current_width - - # Use the smaller scaling factor to maintain aspect ratio - scale = min(scale_h, scale_w) - - # Calculate new dimensions - new_height = int(current_height * scale) - new_width = int(current_width * scale) - - # Resize the image - resized = scipy.ndimage.zoom( - img_array, - (scale, scale, 1) if len(img_array.shape) == 3 else (scale, scale), - order=1, - mode='constant', - cval=0 - ) - - # Create a new array with target shape - result = np.zeros(target_shape, dtype=img_array.dtype) - - # Calculate padding - pad_h = (target_height - new_height) // 2 - pad_w = (target_width - new_width) // 2 - - # Copy the resized image into the center of the result - result[pad_h:pad_h + new_height, pad_w:pad_w + new_width] = resized - - return result \ No newline at end of file From c7ec3f474f5f686fb4e1211bb913350ee255d0d6 Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:49:05 +0330 Subject: [PATCH 3/6] Add files via upload --- Cargo.toml | 13 +++++++++++++ src/lib.rs | 20 ++++++++++++++++++++ src/main.rs | 33 +++++++++++++++++++++++++++++++++ src/main.rs~ | 3 +++ src/sort/brightness.rs | 26 ++++++++++++++++++++++++++ src/sort/hue.rs | 29 +++++++++++++++++++++++++++++ src/sort/lightness.rs | 29 +++++++++++++++++++++++++++++ src/sort/mod.rs | 23 +++++++++++++++++++++++ src/sort/saturation.rs | 29 +++++++++++++++++++++++++++++ 9 files changed, 205 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/main.rs~ create mode 100644 src/sort/brightness.rs create mode 100644 src/sort/hue.rs create mode 100644 src/sort/lightness.rs create mode 100644 src/sort/mod.rs create mode 100644 src/sort/saturation.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..908b5d1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pixfuck" +version = "0.1.1" +edition = "2021" + +[dependencies] +image = "0.25" +rayon = "1.10" +palette = { version = "0.7", features = ["std"] } +clap = { version = "4.5", features = ["derive"] } +log = "0.4" +env_logger = "0.11" +dirs = "4.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a655408 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +pub mod sort; + +use image::{ImageFormat, ImageReader}; +use std::fs::File; +use std::path::Path; + +pub use sort::{SortMode, sort_pixels}; + +pub fn sort_image(input_path: &str, output_path: &str, mode: SortMode) -> image::ImageResult<()> { + let img = ImageReader::open(input_path)?.decode()?.to_rgb8(); + let sorted = sort_pixels(img, mode); + let ext = Path::new(output_path) + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("png"); + + let mut file = File::create(output_path)?; + let format = ImageFormat::from_extension(ext).unwrap_or(ImageFormat::Png); + sorted.write_to(&mut file, format) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2a9e86d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,33 @@ +use pixfuck::{sort_image, SortMode}; +use std::io::{self, Write}; + +fn main() { + println!("Welcome to pixfuck CLI! Please enter input file path:"); + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let input = input.trim(); + + println!("Enter output file path:"); + let mut output = String::new(); + io::stdin().read_line(&mut output).unwrap(); + let output = output.trim(); + + println!("Choose mode: 0=Hue, 1=Saturation, 2=Lightness, 3=Brightness"); + let mut mode_str = String::new(); + io::stdin().read_line(&mut mode_str).unwrap(); + let mode = match mode_str.trim() { + "0" => SortMode::Hue, + "1" => SortMode::Saturation, + "2" => SortMode::Lightness, + "3" => SortMode::Brightness, + _ => { + eprintln!("Invalid mode, defaulting to Hue"); + SortMode::Hue + } + }; + + match sort_image(input, output, mode) { + Ok(_) => println!("Image sorted and saved! 🎉"), + Err(e) => eprintln!("Error: {}", e), + } +} diff --git a/src/main.rs~ b/src/main.rs~ new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs~ @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/sort/brightness.rs b/src/sort/brightness.rs new file mode 100644 index 0000000..53447f2 --- /dev/null +++ b/src/sort/brightness.rs @@ -0,0 +1,26 @@ +use image::{Rgb, RgbImage}; +use rayon::prelude::*; + +pub fn sort(img: RgbImage) -> RgbImage { + let (w, h) = img.dimensions(); + let mut rows: Vec>> = (0..h) + .map(|y| (0..w).map(|x| *img.get_pixel(x, y)).collect()) + .collect(); + + rows.par_iter_mut().for_each(|row| { + row.sort_by(|a, b| brightness(a).total_cmp(&brightness(b))); + }); + + let mut out = RgbImage::new(w, h); + for (y, row) in rows.iter().enumerate() { + for (x, px) in row.iter().enumerate() { + out.put_pixel(x as u32, y as u32, *px); + } + } + + out +} + +fn brightness(rgb: &Rgb) -> f32 { + 0.299 * rgb[0] as f32 + 0.587 * rgb[1] as f32 + 0.114 * rgb[2] as f32 +} diff --git a/src/sort/hue.rs b/src/sort/hue.rs new file mode 100644 index 0000000..f8954ec --- /dev/null +++ b/src/sort/hue.rs @@ -0,0 +1,29 @@ +use image::{Rgb, RgbImage}; +use rayon::prelude::*; +use palette::{Srgb, Hsl, IntoColor}; + +pub fn sort(img: RgbImage) -> RgbImage { + let (w, h) = img.dimensions(); + let mut rows: Vec>> = (0..h) + .map(|y| (0..w).map(|x| *img.get_pixel(x, y)).collect()) + .collect(); + + rows.par_iter_mut().for_each(|row| { + row.sort_by(|a, b| hue(a).partial_cmp(&hue(b)).unwrap()); + }); + + let mut out = RgbImage::new(w, h); + for (y, row) in rows.iter().enumerate() { + for (x, px) in row.iter().enumerate() { + out.put_pixel(x as u32, y as u32, *px); + } + } + + out +} + +fn hue(rgb: &Rgb) -> f32 { + let Srgb { red, green, blue, .. } = Srgb::new(rgb[0], rgb[1], rgb[2]).into_format::(); + let hsl: Hsl = Srgb::new(red, green, blue).into_color(); + hsl.hue.into_degrees() +} diff --git a/src/sort/lightness.rs b/src/sort/lightness.rs new file mode 100644 index 0000000..84523e5 --- /dev/null +++ b/src/sort/lightness.rs @@ -0,0 +1,29 @@ +use image::{Rgb, RgbImage}; +use rayon::prelude::*; +use palette::{Srgb, Hsl, IntoColor}; + +pub fn sort(img: RgbImage) -> RgbImage { + let (w, h) = img.dimensions(); + let mut rows: Vec>> = (0..h) + .map(|y| (0..w).map(|x| *img.get_pixel(x, y)).collect()) + .collect(); + + rows.par_iter_mut().for_each(|row| { + row.sort_by(|a, b| light(a).partial_cmp(&light(b)).unwrap()); + }); + + let mut out = RgbImage::new(w, h); + for (y, row) in rows.iter().enumerate() { + for (x, px) in row.iter().enumerate() { + out.put_pixel(x as u32, y as u32, *px); + } + } + + out +} + +fn light(rgb: &Rgb) -> f32 { + let Srgb { red, green, blue, .. } = Srgb::new(rgb[0], rgb[1], rgb[2]).into_format::(); + let hsl: Hsl = Srgb::new(red, green, blue).into_color(); + hsl.lightness +} diff --git a/src/sort/mod.rs b/src/sort/mod.rs new file mode 100644 index 0000000..7c1b16a --- /dev/null +++ b/src/sort/mod.rs @@ -0,0 +1,23 @@ +pub mod brightness; +pub mod hue; +pub mod lightness; +pub mod saturation; + +use image::RgbImage; + +#[derive(Clone, Copy)] +pub enum SortMode { + Hue, + Saturation, + Lightness, + Brightness, +} + +pub fn sort_pixels(img: RgbImage, mode: SortMode) -> RgbImage { + match mode { + SortMode::Hue => hue::sort(img), + SortMode::Saturation => saturation::sort(img), + SortMode::Lightness => lightness::sort(img), + SortMode::Brightness => brightness::sort(img), + } +} diff --git a/src/sort/saturation.rs b/src/sort/saturation.rs new file mode 100644 index 0000000..2f12a65 --- /dev/null +++ b/src/sort/saturation.rs @@ -0,0 +1,29 @@ +use image::{Rgb, RgbImage}; +use rayon::prelude::*; +use palette::{Srgb, Hsl, IntoColor}; + +pub fn sort(img: RgbImage) -> RgbImage { + let (w, h) = img.dimensions(); + let mut rows: Vec>> = (0..h) + .map(|y| (0..w).map(|x| *img.get_pixel(x, y)).collect()) + .collect(); + + rows.par_iter_mut().for_each(|row| { + row.sort_by(|a, b| sat(a).partial_cmp(&sat(b)).unwrap()); + }); + + let mut out = RgbImage::new(w, h); + for (y, row) in rows.iter().enumerate() { + for (x, px) in row.iter().enumerate() { + out.put_pixel(x as u32, y as u32, *px); + } + } + + out +} + +fn sat(rgb: &Rgb) -> f32 { + let Srgb { red, green, blue, .. } = Srgb::new(rgb[0], rgb[1], rgb[2]).into_format::(); + let hsl: Hsl = Srgb::new(red, green, blue).into_color(); + hsl.saturation +} From 312cc4f77948c042f01b2787e11d2aab042d90e2 Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:49:33 +0330 Subject: [PATCH 4/6] Delete requirements.txt --- requirements.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 28e7b33..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -PyQt6>=6.4.0 -Pillow>=9.0.0 -numpy>=1.20.0 -numba>=0.55.0 -scipy>=1.7.0 \ No newline at end of file From 050e01fdeda44a112aeb24a2cc941c106f49d312 Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:52:54 +0330 Subject: [PATCH 5/6] Delete src/main.rs~ --- src/main.rs~ | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/main.rs~ diff --git a/src/main.rs~ b/src/main.rs~ deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs~ +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} From 296e48ca0e5d663950a1794fee85fe38716bdd23 Mon Sep 17 00:00:00 2001 From: coast <141884103+coasteen@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:53:34 +0330 Subject: [PATCH 6/6] Update main.rs --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2a9e86d..34a1138 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use pixfuck::{sort_image, SortMode}; use std::io::{self, Write}; fn main() { - println!("Welcome to pixfuck CLI! Please enter input file path:"); + println!("Welcome to Pixfuck! Please enter input file realpath:"); let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); let input = input.trim(); @@ -27,7 +27,7 @@ fn main() { }; match sort_image(input, output, mode) { - Ok(_) => println!("Image sorted and saved! 🎉"), + Ok(_) => println!("Image sorted and saved to this directory!"), Err(e) => eprintln!("Error: {}", e), } }