diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..5379e33
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..be5f5c6
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d31b37a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3793a77
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ org.example
+ mark-tools-java
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ org.example.Main
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/example/ImageDotMarker.java b/src/main/java/org/example/ImageDotMarker.java
new file mode 100644
index 0000000..d096a85
--- /dev/null
+++ b/src/main/java/org/example/ImageDotMarker.java
@@ -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 points = new ArrayList<>();
+ private DefaultListModel pointListModel;
+ private JList 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;
+ }
+ }
+}
diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java
new file mode 100644
index 0000000..f250c86
--- /dev/null
+++ b/src/main/java/org/example/Main.java
@@ -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);
+ });
+ }
+}