149 lines
No EOL
6.3 KiB
Python
149 lines
No EOL
6.3 KiB
Python
"""
|
||
Виджет для отображения и манипуляции солнечными изображениями
|
||
Поддерживает: зум, панорамирование, композитинг слоев
|
||
"""
|
||
|
||
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) |