初步可用

This commit is contained in:
2025-10-16 01:13:36 +08:00
parent 0ce9174c93
commit 9a11311e7e
9 changed files with 694 additions and 0 deletions
@@ -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;
}
}
}
+21
View File
@@ -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);
});
}
}