You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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请求与打印机通信,获取扫描文档,无需依赖第三方库。步骤大致为:
    1. 通过SSDP发现网络中的eSCL设备
    2. 向设备的扫描端点发送POST请求配置扫描参数(分辨率、格式、色彩等)
    3. 获取扫描结果的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 18:55:55