初步可用
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="mark-tools-java" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+20
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="http://maven.aliyun.com/nexus/content/repositories/central/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.example</groupId>
|
||||||
|
<artifactId>mark-tools-java</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>org.example.Main</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,571 @@
|
|||||||
|
package org.example;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ImageDotMarker extends JFrame {
|
||||||
|
private BufferedImage originalImage;
|
||||||
|
private BufferedImage displayImage;
|
||||||
|
private JLabel imageLabel;
|
||||||
|
private final List<MarkPoint> points = new ArrayList<>();
|
||||||
|
private DefaultListModel<String> pointListModel;
|
||||||
|
private JList<String> pointList;
|
||||||
|
private static final int DOT_RADIUS = 5;
|
||||||
|
private static final Color DOT_COLOR = Color.RED; // 修复后的颜色常量
|
||||||
|
private String currentImagePath;
|
||||||
|
private int selectedPointIndex = -1;
|
||||||
|
private double scaleFactor = 1.0;
|
||||||
|
private JLabel zoomLabel;
|
||||||
|
private JPanel statusPanel;
|
||||||
|
|
||||||
|
public ImageDotMarker() {
|
||||||
|
setTitle("图片标点工具");
|
||||||
|
setSize(1200, 800);
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// 创建菜单栏
|
||||||
|
createMenuBar();
|
||||||
|
|
||||||
|
// 创建工具栏
|
||||||
|
createToolbar();
|
||||||
|
|
||||||
|
// 创建主面板
|
||||||
|
createMainPanel();
|
||||||
|
|
||||||
|
// 状态栏
|
||||||
|
createStatusBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMenuBar() {
|
||||||
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
JMenu fileMenu = new JMenu("文件");
|
||||||
|
JMenuItem openItem = new JMenuItem("打开图片");
|
||||||
|
JMenuItem saveItem = new JMenuItem("保存图片");
|
||||||
|
JMenuItem exitItem = new JMenuItem("退出");
|
||||||
|
|
||||||
|
openItem.addActionListener(e -> openImage());
|
||||||
|
saveItem.addActionListener(e -> saveImage());
|
||||||
|
exitItem.addActionListener(e -> System.exit(0));
|
||||||
|
|
||||||
|
fileMenu.add(openItem);
|
||||||
|
fileMenu.add(saveItem);
|
||||||
|
fileMenu.addSeparator();
|
||||||
|
fileMenu.add(exitItem);
|
||||||
|
menuBar.add(fileMenu);
|
||||||
|
|
||||||
|
JMenu pointMenu = new JMenu("标记点");
|
||||||
|
JMenuItem clearItem = new JMenuItem("清除所有标记");
|
||||||
|
JMenuItem exportTextItem = new JMenuItem("导出坐标(文本)");
|
||||||
|
|
||||||
|
clearItem.addActionListener(e -> clearPoints());
|
||||||
|
exportTextItem.addActionListener(e -> exportPointsAsText());
|
||||||
|
|
||||||
|
pointMenu.add(clearItem);
|
||||||
|
pointMenu.add(exportTextItem);
|
||||||
|
menuBar.add(pointMenu);
|
||||||
|
|
||||||
|
setJMenuBar(menuBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createToolbar() {
|
||||||
|
JToolBar toolBar = new JToolBar();
|
||||||
|
toolBar.setFloatable(false);
|
||||||
|
|
||||||
|
JButton openBtn = new JButton("打开");
|
||||||
|
openBtn.setToolTipText("打开图片");
|
||||||
|
openBtn.addActionListener(e -> openImage());
|
||||||
|
|
||||||
|
JButton saveBtn = new JButton("保存");
|
||||||
|
saveBtn.setToolTipText("保存图片");
|
||||||
|
saveBtn.addActionListener(e -> saveImage());
|
||||||
|
|
||||||
|
JButton clearBtn = new JButton("清除");
|
||||||
|
clearBtn.setToolTipText("清除标记");
|
||||||
|
clearBtn.addActionListener(e -> clearPoints());
|
||||||
|
|
||||||
|
JButton exportTextBtn = new JButton("导出文本");
|
||||||
|
exportTextBtn.setToolTipText("导出坐标为文本");
|
||||||
|
exportTextBtn.addActionListener(e -> exportPointsAsText());
|
||||||
|
|
||||||
|
// 缩放控件
|
||||||
|
JButton zoomInBtn = new JButton("放大");
|
||||||
|
zoomInBtn.addActionListener(e -> zoomImage(1.25));
|
||||||
|
|
||||||
|
JButton zoomOutBtn = new JButton("缩小");
|
||||||
|
zoomOutBtn.addActionListener(e -> zoomImage(0.8));
|
||||||
|
|
||||||
|
JButton resetZoomBtn = new JButton("重置");
|
||||||
|
resetZoomBtn.addActionListener(e -> resetZoom());
|
||||||
|
|
||||||
|
toolBar.add(openBtn);
|
||||||
|
toolBar.add(saveBtn);
|
||||||
|
toolBar.addSeparator();
|
||||||
|
toolBar.add(clearBtn);
|
||||||
|
toolBar.add(exportTextBtn);
|
||||||
|
toolBar.addSeparator();
|
||||||
|
toolBar.add(zoomInBtn);
|
||||||
|
toolBar.add(zoomOutBtn);
|
||||||
|
toolBar.add(resetZoomBtn);
|
||||||
|
|
||||||
|
add(toolBar, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMainPanel() {
|
||||||
|
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||||
|
splitPane.setDividerLocation(300);
|
||||||
|
splitPane.setResizeWeight(0.7);
|
||||||
|
|
||||||
|
// 左侧图片面板
|
||||||
|
JPanel imagePanel = new JPanel(new BorderLayout());
|
||||||
|
imageLabel = new JLabel();
|
||||||
|
imageLabel.setHorizontalAlignment(JLabel.CENTER);
|
||||||
|
|
||||||
|
// 添加鼠标监听器
|
||||||
|
imageLabel.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (originalImage != null) {
|
||||||
|
Point imgPoint = getImagePoint(e.getPoint());
|
||||||
|
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
// 双击编辑点
|
||||||
|
MarkPoint point = findPointAt(imgPoint);
|
||||||
|
if (point != null) {
|
||||||
|
editPointName(point);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 单击添加点
|
||||||
|
addNewPoint(imgPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加鼠标移动监听器以显示坐标
|
||||||
|
imageLabel.addMouseMotionListener(new MouseMotionAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(MouseEvent e) {
|
||||||
|
if (originalImage != null) {
|
||||||
|
Point imgPoint = getImagePoint(e.getPoint());
|
||||||
|
updateStatusBar("当前位置: (" + imgPoint.x + ", " + imgPoint.y + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
imagePanel.add(new JScrollPane(imageLabel), BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 右侧标记点面板
|
||||||
|
JPanel pointPanel = new JPanel(new BorderLayout());
|
||||||
|
pointPanel.setBorder(BorderFactory.createTitledBorder("标记点管理"));
|
||||||
|
|
||||||
|
// 点列表
|
||||||
|
pointListModel = new DefaultListModel<>();
|
||||||
|
pointList = new JList<>(pointListModel);
|
||||||
|
pointList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
pointList.addListSelectionListener(e -> {
|
||||||
|
if (!e.getValueIsAdjusting()) {
|
||||||
|
selectedPointIndex = pointList.getSelectedIndex();
|
||||||
|
if (selectedPointIndex >= 0 && selectedPointIndex < points.size()) {
|
||||||
|
highlightSelectedPoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加双击编辑名称功能
|
||||||
|
pointList.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
int index = pointList.locationToIndex(e.getPoint());
|
||||||
|
if (index >= 0 && index < points.size()) {
|
||||||
|
editPointName(points.get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JScrollPane listScrollPane = new JScrollPane(pointList);
|
||||||
|
listScrollPane.setPreferredSize(new Dimension(280, 400));
|
||||||
|
|
||||||
|
// 点操作按钮
|
||||||
|
JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 5, 5));
|
||||||
|
|
||||||
|
JButton deleteBtn = new JButton("删除");
|
||||||
|
deleteBtn.addActionListener(e -> deleteSelectedPoint());
|
||||||
|
|
||||||
|
JButton moveUpBtn = new JButton("上移");
|
||||||
|
moveUpBtn.addActionListener(e -> movePointUp());
|
||||||
|
|
||||||
|
JButton moveDownBtn = new JButton("下移");
|
||||||
|
moveDownBtn.addActionListener(e -> movePointDown());
|
||||||
|
|
||||||
|
buttonPanel.add(deleteBtn);
|
||||||
|
buttonPanel.add(moveUpBtn);
|
||||||
|
buttonPanel.add(moveDownBtn);
|
||||||
|
|
||||||
|
pointPanel.add(listScrollPane, BorderLayout.CENTER);
|
||||||
|
pointPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
splitPane.setLeftComponent(imagePanel);
|
||||||
|
splitPane.setRightComponent(pointPanel);
|
||||||
|
|
||||||
|
add(splitPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createStatusBar() {
|
||||||
|
this.statusPanel = new JPanel(new BorderLayout());
|
||||||
|
this.statusPanel.setBorder(BorderFactory.createEtchedBorder());
|
||||||
|
|
||||||
|
JLabel statusLabel = new JLabel("就绪");
|
||||||
|
statusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
|
||||||
|
this.statusPanel.add(statusLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
zoomLabel = new JLabel("缩放: 100%");
|
||||||
|
zoomLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
|
||||||
|
this.statusPanel.add(zoomLabel, BorderLayout.EAST);
|
||||||
|
|
||||||
|
add(this.statusPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatusBar() {
|
||||||
|
updateStatusBar("已添加标记点: " + points.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatusBar(String message) {
|
||||||
|
// 直接使用类成员变量,避免查找和类型转换错误
|
||||||
|
JLabel statusLabel = (JLabel) statusPanel.getComponent(0);
|
||||||
|
statusLabel.setText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openImage() {
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||||
|
"图片文件 (*.jpg, *.jpeg, *.png, *.gif)", "jpg", "jpeg", "png", "gif"));
|
||||||
|
|
||||||
|
int result = fileChooser.showOpenDialog(this);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
File file = fileChooser.getSelectedFile();
|
||||||
|
currentImagePath = file.getAbsolutePath();
|
||||||
|
originalImage = ImageIO.read(file);
|
||||||
|
resetZoom();
|
||||||
|
points.clear();
|
||||||
|
updatePointList();
|
||||||
|
|
||||||
|
updateStatusBar("已打开: " + file.getName());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "无法打开图片: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveImage() {
|
||||||
|
if (originalImage == null) return;
|
||||||
|
|
||||||
|
// 创建带标记点的图片
|
||||||
|
BufferedImage markedImage = createMarkedImage();
|
||||||
|
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setSelectedFile(new File("marked_image.png"));
|
||||||
|
fileChooser.setFileFilter(new FileNameExtensionFilter("PNG图片 (*.png)", "png"));
|
||||||
|
|
||||||
|
int result = fileChooser.showSaveDialog(this);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
File file = fileChooser.getSelectedFile();
|
||||||
|
if (!file.getName().toLowerCase().endsWith(".png")) {
|
||||||
|
file = new File(file.getAbsolutePath() + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageIO.write(markedImage, "png", file);
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(this, "图片保存成功!");
|
||||||
|
updateStatusBar("已保存: " + file.getName());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "保存失败: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage createMarkedImage() {
|
||||||
|
BufferedImage markedImage = new BufferedImage(
|
||||||
|
originalImage.getWidth(),
|
||||||
|
originalImage.getHeight(),
|
||||||
|
BufferedImage.TYPE_INT_ARGB
|
||||||
|
);
|
||||||
|
|
||||||
|
Graphics2D g = markedImage.createGraphics();
|
||||||
|
g.drawImage(originalImage, 0, 0, null);
|
||||||
|
|
||||||
|
// 绘制所有标记点
|
||||||
|
for (MarkPoint point : points) {
|
||||||
|
drawPoint(g, point, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
return markedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zoomImage(double factor) {
|
||||||
|
scaleFactor *= factor;
|
||||||
|
updateImageDisplay();
|
||||||
|
zoomLabel.setText(String.format("缩放: %.0f%%", scaleFactor * 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetZoom() {
|
||||||
|
scaleFactor = 1.0;
|
||||||
|
updateImageDisplay();
|
||||||
|
zoomLabel.setText("缩放: 100%");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateImageDisplay() {
|
||||||
|
if (originalImage == null) return;
|
||||||
|
|
||||||
|
int newWidth = (int) (originalImage.getWidth() * scaleFactor);
|
||||||
|
int newHeight = (int) (originalImage.getHeight() * scaleFactor);
|
||||||
|
|
||||||
|
displayImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = displayImage.createGraphics();
|
||||||
|
|
||||||
|
// 设置平滑渲染
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// 缩放并绘制原始图片
|
||||||
|
AffineTransform transform = AffineTransform.getScaleInstance(scaleFactor, scaleFactor);
|
||||||
|
g.drawImage(originalImage, transform, this);
|
||||||
|
|
||||||
|
// 绘制所有标记点
|
||||||
|
for (MarkPoint point : points) {
|
||||||
|
drawPoint(g, point, scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
imageLabel.setIcon(new ImageIcon(displayImage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPoint(Graphics2D g, MarkPoint point, double scale) {
|
||||||
|
int scaledX = (int) (point.x * scale);
|
||||||
|
int scaledY = (int) (point.y * scale);
|
||||||
|
int scaledRadius = (int) (DOT_RADIUS * scale);
|
||||||
|
|
||||||
|
// 绘制点
|
||||||
|
g.setColor(DOT_COLOR);
|
||||||
|
g.fillOval(scaledX - scaledRadius, scaledY - scaledRadius, scaledRadius * 2, scaledRadius * 2);
|
||||||
|
g.setColor(Color.WHITE);
|
||||||
|
g.drawOval(scaledX - scaledRadius, scaledY - scaledRadius, scaledRadius * 2, scaledRadius * 2);
|
||||||
|
|
||||||
|
// 绘制点名称
|
||||||
|
if (point.name != null && !point.name.isEmpty()) {
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.setFont(new Font("Arial", Font.BOLD, (int)(12 * scale)));
|
||||||
|
g.drawString(point.name, scaledX + scaledRadius + 2, scaledY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point getImagePoint(Point mousePoint) {
|
||||||
|
Rectangle bounds = imageLabel.getBounds();
|
||||||
|
int imgWidth = (int)(originalImage.getWidth() * scaleFactor);
|
||||||
|
int imgHeight = (int)(originalImage.getHeight() * scaleFactor);
|
||||||
|
|
||||||
|
// 计算图片在标签中的实际显示位置
|
||||||
|
int xOffset = (bounds.width - imgWidth) / 2;
|
||||||
|
int yOffset = (bounds.height - imgHeight) / 2;
|
||||||
|
|
||||||
|
// 计算相对于图片的坐标
|
||||||
|
int x = mousePoint.x - xOffset;
|
||||||
|
int y = mousePoint.y - yOffset;
|
||||||
|
|
||||||
|
// 确保坐标在图片范围内
|
||||||
|
x = Math.max(0, Math.min(x, imgWidth - 1));
|
||||||
|
y = Math.max(0, Math.min(y, imgHeight - 1));
|
||||||
|
|
||||||
|
// 转换为原始图片坐标
|
||||||
|
return new Point(
|
||||||
|
(int)(x / scaleFactor),
|
||||||
|
(int)(y / scaleFactor)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewPoint(Point imgPoint) {
|
||||||
|
// 提示用户输入点名称
|
||||||
|
String name = JOptionPane.showInputDialog(this, "请输入标记点名称:", "添加标记点", JOptionPane.PLAIN_MESSAGE);
|
||||||
|
if (name == null) return; // 用户取消
|
||||||
|
|
||||||
|
MarkPoint point = new MarkPoint(imgPoint.x, imgPoint.y, name);
|
||||||
|
points.add(point);
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
updateStatusBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MarkPoint findPointAt(Point imgPoint) {
|
||||||
|
int tolerance = (int)(DOT_RADIUS / scaleFactor);
|
||||||
|
|
||||||
|
for (MarkPoint point : points) {
|
||||||
|
double distance = Math.sqrt(Math.pow(point.x - imgPoint.x, 2) + Math.pow(point.y - imgPoint.y, 2));
|
||||||
|
if (distance <= tolerance) {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editPointName(MarkPoint point) {
|
||||||
|
String newName = JOptionPane.showInputDialog(this, "修改标记点名称:", point.name);
|
||||||
|
if (newName != null && !newName.equals(point.name)) {
|
||||||
|
point.name = newName;
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearPoints() {
|
||||||
|
if (originalImage == null) return;
|
||||||
|
|
||||||
|
int confirm = JOptionPane.showConfirmDialog(this,
|
||||||
|
"确定要清除所有标记点吗?", "清除标记",
|
||||||
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
|
||||||
|
if (confirm == JOptionPane.YES_OPTION) {
|
||||||
|
points.clear();
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
updateStatusBar("已清除所有标记点");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportPointsAsText() {
|
||||||
|
if (points.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "当前没有标记点");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("图片标记点坐标:\n");
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
MarkPoint p = points.get(i);
|
||||||
|
sb.append(String.format("%d. %s: (%d, %d)\n", i + 1, p.name, p.x, p.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
JTextArea textArea = new JTextArea(sb.toString());
|
||||||
|
textArea.setEditable(false);
|
||||||
|
JScrollPane scrollPane = new JScrollPane(textArea);
|
||||||
|
scrollPane.setPreferredSize(new Dimension(300, 200));
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(this, scrollPane, "标记点坐标", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePointList() {
|
||||||
|
pointListModel.clear();
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
MarkPoint p = points.get(i);
|
||||||
|
pointListModel.addElement(String.format("%d. %s: (%d, %d)", i + 1, p.name, p.x, p.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteSelectedPoint() {
|
||||||
|
if (selectedPointIndex == -1) {
|
||||||
|
JOptionPane.showMessageDialog(this, "请先选择一个标记点");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
points.remove(selectedPointIndex);
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
selectedPointIndex = -1;
|
||||||
|
updateStatusBar("已删除标记点");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void movePointUp() {
|
||||||
|
if (selectedPointIndex == -1 || selectedPointIndex == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkPoint point = points.get(selectedPointIndex);
|
||||||
|
points.remove(selectedPointIndex);
|
||||||
|
points.add(selectedPointIndex - 1, point);
|
||||||
|
selectedPointIndex--;
|
||||||
|
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
pointList.setSelectedIndex(selectedPointIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void movePointDown() {
|
||||||
|
if (selectedPointIndex == -1 || selectedPointIndex == points.size() - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkPoint point = points.get(selectedPointIndex);
|
||||||
|
points.remove(selectedPointIndex);
|
||||||
|
points.add(selectedPointIndex + 1, point);
|
||||||
|
selectedPointIndex++;
|
||||||
|
|
||||||
|
updateImageDisplay();
|
||||||
|
updatePointList();
|
||||||
|
pointList.setSelectedIndex(selectedPointIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightSelectedPoint() {
|
||||||
|
if (selectedPointIndex >= 0 && selectedPointIndex < points.size()) {
|
||||||
|
updateImageDisplay();
|
||||||
|
|
||||||
|
// 高亮显示选中的点
|
||||||
|
MarkPoint p = points.get(selectedPointIndex);
|
||||||
|
Graphics2D g = displayImage.createGraphics();
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
int scaledX = (int) (p.x * scaleFactor);
|
||||||
|
int scaledY = (int) (p.y * scaleFactor);
|
||||||
|
int scaledRadius = (int) (DOT_RADIUS * scaleFactor);
|
||||||
|
|
||||||
|
// 绘制高亮点
|
||||||
|
g.setColor(Color.YELLOW);
|
||||||
|
g.fillOval(scaledX - scaledRadius, scaledY - scaledRadius, scaledRadius * 2, scaledRadius * 2);
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.drawOval(scaledX - scaledRadius, scaledY - scaledRadius, scaledRadius * 2, scaledRadius * 2);
|
||||||
|
|
||||||
|
// 绘制名称
|
||||||
|
if (p.name != null && !p.name.isEmpty()) {
|
||||||
|
g.setColor(Color.BLUE);
|
||||||
|
g.setFont(new Font("Arial", Font.BOLD, (int)(12 * scaleFactor)));
|
||||||
|
g.drawString(p.name, scaledX + scaledRadius + 2, scaledY);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
imageLabel.setIcon(new ImageIcon(displayImage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
ImageDotMarker app = new ImageDotMarker();
|
||||||
|
app.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记点类,包含坐标和名称
|
||||||
|
private static class MarkPoint {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
String name;
|
||||||
|
|
||||||
|
public MarkPoint(int x, int y, String name) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.example;
|
||||||
|
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序主入口类
|
||||||
|
* 用于启动图片标点工具应用程序
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
/**
|
||||||
|
* 程序入口方法
|
||||||
|
* @param args 命令行参数
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 使用SwingUtilities.invokeLater确保UI组件在事件调度线程中创建
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
ImageDotMarker app = new ImageDotMarker();
|
||||||
|
app.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user