fuck yeah!

This commit is contained in:
Vic Sergeev 2026-06-10 17:33:12 +03:00
parent ccb53d9091
commit da10f5e132
44 changed files with 3260 additions and 448 deletions

149
views/canvas_widget.py Normal file
View file

@ -0,0 +1,149 @@
"""
Виджет для отображения и манипуляции солнечными изображениями
Поддерживает: зум, панорамирование, композитинг слоев
"""
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
from PySide6.QtCore import Qt, QRectF, QPointF, Signal
from PySide6.QtGui import QPixmap, QImage, QWheelEvent, QMouseEvent, QPainter
import numpy as np
class SolarCanvas(QGraphicsView):
"""
Кастомный Canvas для отображения солнечных изображений
Поддерживает масштабирование, панорамирование и наложение слоев
"""
zoom_changed = Signal(float)
def __init__(self, controller):
super().__init__()
self.controller = controller
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
# Настройки отображения - ИСПРАВЛЕНО: используем QPainter
self.setRenderHint(QPainter.RenderHint.Antialiasing)
self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
self.setDragMode(self.DragMode.ScrollHandDrag)
self.setTransformationAnchor(self.ViewportAnchor.AnchorUnderMouse)
self.setResizeAnchor(self.ViewportAnchor.AnchorUnderMouse)
# Переменные для зума
self.current_zoom = 1.0
self.min_zoom = 0.1
self.max_zoom = 10.0
self.zoom_factor = 1.1
# Словарь для хранения слоев (QGraphicsPixmapItem)
self.layer_items = {}
# Фон для лучшего контраста (черный для астрономических изображений)
self.setBackgroundBrush(Qt.GlobalColor.black)
# Дополнительные настройки для плавности
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
def set_image(self, layer_id: int, image_data: np.ndarray):
"""
Устанавливает изображение для слоя
Args:
layer_id: ID слоя
image_data: numpy массив с изображением
"""
# Конвертируем numpy array в QImage
height, width = image_data.shape[:2]
if len(image_data.shape) == 2:
# Черно-белое изображение
bytes_per_line = width
qimage = QImage(image_data.data, width, height, bytes_per_line, QImage.Format.Format_Grayscale8)
else:
# RGB изображение
bytes_per_line = 3 * width
qimage = QImage(image_data.data, width, height, bytes_per_line, QImage.Format.Format_RGB888)
# Конвертируем в QPixmap
pixmap = QPixmap.fromImage(qimage)
# Создаем или обновляем графический элемент
if layer_id in self.layer_items:
self.layer_items[layer_id].setPixmap(pixmap)
else:
item = self.scene.addPixmap(pixmap)
self.layer_items[layer_id] = item
# Центрируем изображение при первом добавлении
if len(self.layer_items) == 1:
self.centerOn(item)
self.fitInView(item, Qt.AspectRatioMode.KeepAspectRatio)
def set_layer_visibility(self, layer_id: int, visible: bool):
"""Устанавливает видимость слоя"""
if layer_id in self.layer_items:
self.layer_items[layer_id].setVisible(visible)
def set_layer_opacity(self, layer_id: int, opacity: float):
"""Устанавливает прозрачность слоя"""
if layer_id in self.layer_items:
self.layer_items[layer_id].setOpacity(opacity)
def remove_layer(self, layer_id: int):
"""Удаляет слой"""
if layer_id in self.layer_items:
self.scene.removeItem(self.layer_items[layer_id])
del self.layer_items[layer_id]
def clear_all_layers(self):
"""Очищает все слои"""
for item in self.layer_items.values():
self.scene.removeItem(item)
self.layer_items.clear()
def wheelEvent(self, event: QWheelEvent):
"""Обработка колесика мыши для зума"""
# Определяем направление зума
zoom_in = event.angleDelta().y() > 0
# Вычисляем новый уровень зума
if zoom_in:
new_zoom = self.current_zoom * self.zoom_factor
else:
new_zoom = self.current_zoom / self.zoom_factor
# Ограничиваем зум
if self.min_zoom <= new_zoom <= self.max_zoom:
self.current_zoom = new_zoom
self.scale(self.zoom_factor if zoom_in else 1/self.zoom_factor,
self.zoom_factor if zoom_in else 1/self.zoom_factor)
self.zoom_changed.emit(self.current_zoom)
def mousePressEvent(self, event: QMouseEvent):
"""Обработка нажатия мыши для панорамирования"""
if event.button() == Qt.MouseButton.MiddleButton:
self.setDragMode(self.DragMode.ScrollHandDrag)
# Создаем фейковое событие с левой кнопкой
fake_event = QMouseEvent(
event.type(), event.pos(), Qt.MouseButton.LeftButton,
Qt.MouseButton.LeftButton, event.modifiers()
)
super().mousePressEvent(fake_event)
else:
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent):
"""Обработка отпускания мыши"""
if event.button() == Qt.MouseButton.MiddleButton:
self.setDragMode(self.DragMode.NoDrag)
super().mouseReleaseEvent(event)
def reset_view(self):
"""Сбрасывает зум и позицию"""
if self.layer_items:
first_item = next(iter(self.layer_items.values()))
self.fitInView(first_item, Qt.AspectRatioMode.KeepAspectRatio)
self.current_zoom = 1.0
self.zoom_changed.emit(self.current_zoom)