初步可用
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