HelioParser/views/canvas_widget.py
2026-06-10 17:33:12 +03:00

149 lines
No EOL
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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