Java实现网络/共享打印机扫描仪连接的免费方案求助
需求与问题
正在开发Java解决方案,实现连接打印机并通过其内置扫描仪获取扫描文档。当前无法直接访问网络或共享资源中的打印机,已尝试Twain、Morena、mmscomputing、WIA、SANE等库/方案,仅Morena可实现USB连接打印机的扫描功能,寻求免费实现网络打印机扫描的方案或工具。
免费网络扫描方案推荐
- SANE(跨平台,Linux为主)
可通过java-sane绑定(如jSANE)配合SANE的网络扫描功能实现。SANE支持网络扫描仪共享,只需在扫描仪所在主机配置SANE服务器,Java端通过网络连接到该服务器即可扫描。注意:Windows下SANE支持有限,需额外安装驱动;Linux/macOS原生支持较好,完全免费开源。 - eSCL协议(基于HTTP)
现代网络打印机大多支持eSCL(AirScan)协议,这是一种基于HTTP的扫描标准。可直接通过Java发送HTTP请求与打印机通信,获取扫描文档,无需依赖第三方库。步骤大致为:- 通过SSDP发现网络中的eSCL设备
- 向设备的扫描端点发送POST请求配置扫描参数(分辨率、格式、色彩等)
- 获取扫描结果的URL,下载图片文件
- Windows原生WIA(仅Windows平台)
若仅针对Windows环境,可通过JNA/JNI调用Windows的WIA API,WIA支持网络共享扫描仪。可编写JNI绑定或使用现成的JNA封装(如jna-platform中的相关类),实现对网络扫描仪的枚举和扫描操作,完全免费。 - CUPS(Linux/macOS)
CUPS不仅支持打印,也支持扫描功能(通过cups-scan)。Java可通过调用CUPS的命令行工具(如scanimage)或HTTP API实现扫描,CUPS支持网络扫描仪,适合Linux/macOS平台的免费方案。
现有Morena USB扫描可用代码
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.image.ImageConsumer; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.JApplet; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.WindowConstants; import javax.swing.border.LineBorder; import javax.swing.filechooser.FileFilter; import SK.gnome.morena.Morena; import SK.gnome.morena.MorenaImage; import SK.gnome.morena.MorenaSource; import SK.gnome.twain.TwainSource; import SK.gnome.twain.TwainSource.Frame; public class ScannerApp extends JApplet { private static class MainPanel extends JPanel { private JTextField status = new JTextField(); private ImagePanel selected=null; private SaveImageAction saveImageAction; private UploadImageAction uploadImageAction; private MouseListener mouseListener=new MouseListener(); private boolean hasServer=false; private URL documentBase=null; private class RemoveAllAction extends AbstractAction implements Runnable { RemoveAllAction() { super("remove all"); } public synchronized void actionPerformed(ActionEvent event) { new Thread(this).start(); } public synchronized void run() { removeAll(); select(null); repaint(); } } private class AcquireImageAction extends AbstractAction implements Runnable { AcquireImageAction() { super("acquire image"); } public synchronized void actionPerformed(ActionEvent event) { new Thread(this).start(); } public synchronized void run() { try { status.setText("Working ..."); MorenaSource source=Morena.selectSource(MainPanel.this); if (source!=null) { source.setColorMode(); source.setResolution(100); while (true) { MorenaImage morenaImage=new MorenaImage(source); int imageStatus=morenaImage.getStatus(); if (imageStatus==ImageConsumer.STATICIMAGEDONE) { int imageWidth=morenaImage.getWidth(); int imageHeight=morenaImage.getHeight(); int imagePixelSize=morenaImage.getPixelSize(); ImagePanel image=new ImagePanel(Toolkit.getDefaultToolkit().createImage(morenaImage)); MainPanel.this.add(image); select(image); int size=(int)Math.round(Math.sqrt(getComponentCount())); setLayout(new GridLayout(size, size)); if (TwainSource.class.isInstance(source)) { Frame frame=((TwainSource)source).getFrame(); status.setText("Done - actual image frame is ["+Math.round(100*frame.left)/100.0+", "+Math.round(100*frame.top)/100.0+"] - ["+Math.round(100*frame.right)/100.0+", "+Math.round(100*frame.bottom)/100.0+"] ..."); } else status.setText("Done - actual image size is "+imageWidth+" x "+imageHeight+" x "+imagePixelSize+" ..."); validate(); if (TwainSource.class.isInstance(source) && ((TwainSource)source).hasMoreImages()) continue; } else if (imageStatus==ImageConsumer.IMAGEABORTED) status.setText("Aborted, try again ..."); else if (imageStatus==ImageConsumer.IMAGEERROR) status.setText("Failed, try again ..."); break; } } else status.setText("Failed, try again ..."); } catch (NoSuchMethodError error) { JOptionPane.showMessageDialog(MainPanel.this, "Previous version of Morena is installed in "+System.getProperty("java.home")+".\nYou have to remove it first.\nClick OK to terminate browser.", "Error", JOptionPane.ERROR_MESSAGE); System.exit(0); } catch (Throwable exception) { JOptionPane.showMessageDialog(MainPanel.this, exception.toString(), "Error", JOptionPane.ERROR_MESSAGE); exception.printStackTrace(); status.setText("Failed, try again ..."); } finally { try { Morena.close(); } catch (Exception exception) { exception.printStackTrace(); } } } } private class SaveImageAction extends AbstractAction implements Runnable { private class Filter extends FileFilter { String type; Filter(String type) { this.type=type; } public boolean accept(File file) { return file.getName().endsWith(type); } public String getDescription() { return type+" Files"; } } SaveImageAction() { super("save to file"); } public void actionPerformed(ActionEvent event) { new Thread(this).start(); } public synchronized void run() { try { status.setText("Working ..."); Image image=selected.getImage(); BufferedImage bufferedImage=new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); bufferedImage.createGraphics().drawImage(image, 0, 0, null); JFileChooser chooser=new JFileChooser(); // Java 1.6 introduced a comfortable ImageIO.getWriterFileSuffixes() method. String e[] = ImageIO.getWriterFormatNames(); for (int i=0; i<e.length; i++) chooser.addChoosableFileFilter(new Filter(e[i])); int result=chooser.showSaveDialog(MainPanel.this); if (result==JFileChooser.APPROVE_OPTION) { String ext=chooser.getFileFilter().getDescription(); ext=ext.substring(0, ext.indexOf(' ')).toLowerCase(); File file=chooser.getSelectedFile(); String name=file.getName(); if (!name.endsWith(ext)) file=new File(file.getParentFile(), name+"."+ext); ImageIO.write(bufferedImage, ext, file); } } catch (Throwable exception) { JOptionPane.showMessageDialog(MainPanel.this, exception.toString(), "Error", JOptionPane.ERROR_MESSAGE); exception.printStackTrace(); status.setText("Failed, try again ..."); } } public boolean isEnabled() { return selected != null; } } private class UploadImageAction extends AbstractAction implements Runnable { UploadImageAction() { super("upload to server"); } public void actionPerformed(ActionEvent event) { new Thread(this).start(); } public synchronized void run() { try { status.setText("Working ..."); Image image=selected.getImage(); ByteArrayOutputStream tmp=new ByteArrayOutputStream(); BufferedImage bufferedImage=new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); bufferedImage.createGraphics().drawImage(image, 0, 0, null); ImageIO.write(bufferedImage, "jpg", tmp); tmp.close(); int contentLength=tmp.size(); if (contentLength>1024*1024) throw new Exception("Image is too big to upload"); URL uploadURL=new URL(documentBase, "upload.php"); HttpURLConnection connection=(HttpURLConnection)uploadURL.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setUseCaches(false); connection.setDefaultUseCaches(false); connection.setRequestProperty("content-type", "img/jpeg"); connection.setRequestProperty("content-length", String.valueOf(contentLength)); OutputStream out=connection.getOutputStream(); out.write(tmp.toByteArray()); out.close(); InputStream in=connection.getInputStream(); int c; while ((c=in.read())!=-1) System.err.write(c); in.close(); URL imageURL=new URL(documentBase, connection.getHeaderField("file-name")); status.setText("Done - image is uploaded to "+imageURL+" (for at least 5 minutes) ..."); } catch (Throwable exception) { JOptionPane.showMessageDialog(MainPanel.this, exception.toString(), "Error", JOptionPane.ERROR_MESSAGE); exception.printStackTrace(); status.setText("Failed, try again ..."); } } public boolean isEnabled() { return hasServer && selected != null; } } private class MouseListener extends MouseAdapter { public void mouseClicked(MouseEvent event) { select((ImagePanel)event.getComponent()); } } private class ImagePanel extends JPanel { private Image image; int imageWidth; int imageHeight; ImagePanel(Image image) { this.image=image; imageWidth=image.getWidth(null); imageHeight=image.getHeight(null); addMouseListener(mouseListener); } public Image getImage() { return image; } public void paint(Graphics g) { super.paint(g); int panelWidth=getWidth()-6; int panelHeight=getHeight()-6; double horizontalRatio=(double)panelWidth/imageWidth; double verticalRatio=(double)panelHeight/imageHeight; if (horizontalRatio>verticalRatio) g.drawImage(image, (int)(panelWidth-imageWidth*verticalRatio)/2+3, 3, (int)(imageWidth*verticalRatio), (int)(imageHeight*verticalRatio), this); else g.drawImage(image, 3, 3, (int)(imageWidth*horizontalRatio), (int)(imageHeight*horizontalRatio), this); } } private class ToolBar extends JToolBar { ToolBar() { add(new RemoveAllAction()); addSeparator(); add(new AcquireImageAction()); addSeparator(); add(saveImageAction=new SaveImageAction()); saveImageAction.setEnabled(false); addSeparator(); add(uploadImageAction=new UploadImageAction()); uploadImageAction.setEnabled(false); setMargin(new Insets(4, 2, 2, 2)); } } private class StatusBar extends JToolBar { StatusBar() { add(status); status.setText("Ready ..."); } } void select(ImagePanel image) { if(selected != null) selected.setBorder(null); selected = image; if(selected != null) { selected.setBorder(new LineBorder(Color.blue, 1)); saveImageAction.setEnabled(true); uploadImageAction.setEnabled(hasServer); } else { saveImageAction.setEnabled(false); uploadImageAction.setEnabled(false); } } MainPanel(Container container, URL documentBase) { this.documentBase=documentBase; status.setEditable(false); hasServer=documentBase!=null && documentBase.getProtocol().indexOf("http")!=-1; container.add(new ToolBar(), BorderLayout.NORTH); container.add(this, BorderLayout.CENTER); container.add(status, BorderLayout.SOUTH); setLayout(new GridLayout(1, 1)); } } public void init() { new MainPanel(getContentPane(), getDocumentBase()); } public static void main(String args[]) { JFrame frame=new JFrame("Morena Studio"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); new MainPanel(frame.getContentPane(), null); frame.setBounds(100, 100, 600, 400); frame.setVisible(true); } }
内容的提问来源于stack exchange,提问作者mmateo
相关产品推荐
相关产品推荐

