从ServletOutputStream输出GIF

我正在编写一个动态生成GIF文件的端点。 我会从头开始。

我有一个名为Function的类,它的工作方式类似于抽象类,我有几个类,在本例中是AddFunction ,代表了一小部分function。 在这种情况下, AddFunction将一些数字加在一起。 当命中终点时, AddFunction的ID被传递给它(它可以是任何,在这个例子中它是add函数)。 控制器中的代码如下:

 /** * Returns the image for a function */ @RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { Function function = functionService.getFunction(Integer.valueOf(functionId)); Logger logger = Logger.getLogger(FunctionController.class); ServletOutputStream servOut = response.getOutputStream(); // Uses default values if you pass in nulls. function.getImage(servOut, null, null); servOut.flush(); servOut.close(); } 

首先,通过它的ID找到Function 。 我检查过,找到了正确的function。 这段代码需要一些validation(例如检查传入的id是一个有效的数字),但我稍后会讨论。 然后我抓住servlet输出流并将其传递给函数的getImage方法。 此方法生成描述该function的GIF。 此代码如下所示:

 public void getImage(OutputStream out, String staticContent, String changedContent) throws IOException { String[] data = {"2", "+", "2", "=", "4"}; Logger logger = Logger.getLogger(AddFunction.class); logger.info("Getting the add image."); ImageUtils.writeSequenceToImage(ImageIO.createImageOutputStream(out), data, 5, Constants.IMAGE_HEIGHT / 2); } 

如您所见,它忽略了值,并且目前正在使用库存数据。 它创建了一个值数组。 这些值中的每一个都出现在GIF的每个帧中。 所以我做的是我使用ServletOutputStream并使用ImageIO.createImageOutputStream将其与ImageOutputStream对象一起包装。 这是在我自己的ImageUtils类中传递给writeSequenceToImage方法时。 最后两个值是写入位置的坐标。 在这种情况下,图像的垂直中间位于最左侧。 writeSequenceToImage方法的代码如下:

 public static void writeSequenceToImage(ImageOutputStream out, String[] contentList, int x, int y) throws IOException { StringBuilder dataBuilder = new StringBuilder(); Test test = new Test(out, BufferedImage.TYPE_INT_RGB, 500, true); Logger logger = Logger.getLogger(ImageUtils.class); logger.info("Writing sequence to image."); for (String content : contentList) { dataBuilder.append(content); logger.info("writing " + dataBuilder.toString() + " to the gif."); test.writeToSequence(generateAndWriteToImage(dataBuilder.toString(), x, y)); } } 

在此代码中,我使用类Test (临时名称),其中包含将数据写入GIF文件的代码。 我在这里所做的就是循环并将每个值添加到GIF中的帧。 可以在此处找到类Test的代码。 我所做的是构建String,因此在我们的示例中,日志将输出:

 2014-12-31 14:37:15 INFO ImageUtils:48 - Writing sequence to image. 2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2 to the gif. 2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+ to the gif. 2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2 to the gif. 2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2= to the gif. 2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2=4 to the gif. 

这将使GIF的每一帧中的外观形成字符串。 现在,我将其写入GIF,并且我希望它直接推送到ServletOutputStream ,只有当我尝试使用以下HTML引用它时:

 

{{selectedFunction.name}}

{{selectedFunction.description}}

This function expects a {{selectedFunction.expectedParameterType}} as a parameter.

This function will return a {{selectedFunction.expectedReturnType}}

{{selectedFunction.name}}

我在Chrome中看到以下数据返回:

数据响应

我在我的页面上看不到任何图像:

视图

我试过的

我试图看到回来的大小。 为此,我用ByteArrayOutputStream替换了ServletOutputStream ,这样我就可以获得数据的大小。 如果我这样做,我的代码看起来像这样:

 /** * Returns the image for a function */ @RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") public @ResponseBody byte[] getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { Function function = functionService.getFunction(Integer.valueOf(functionId)); Logger logger = Logger.getLogger(FunctionController.class); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Uses default values if you pass in nulls. function.getImage(baos, null, null); logger.info("The number of bytes returned is " + baos.toByteArray().length); return baos.toByteArray(); } 

并且日志输出:

 2014-12-31 15:34:09 INFO FunctionController:85 - The number of bytes returned is 0 

所以这告诉我它也没有被写入。 所以我改变了我的方法并重构了代码,所以我在我的控制器中保留了对ImageOutputStream的引用。 这意味着我可以完全控制对象,所以现在输出日志:

 2014-12-31 15:39:56 INFO FunctionController:85 - The number of bytes returned is 2708 

这是令人鼓舞的! 对于非常简单的GIF,2KB听起来很合适。 但是,当我检查谷歌的回复时,类似的故事:

ios回应

虽然这次它有内容长度,但没有可用的预览,图像仍然没有出现。

我想知道这里是否有人解决了类似的问题? 我怀疑它与GIF的编码有关,但ImageIO不支持从一个流到另一个流的转换,只支持从一个BufferedImage类型到另一个。 所以我使用ImageIO.read方法将其读入BufferedImage并使用ImageIO.write将其作为gif写入ServletOutputStream 。 这产生了以下错误:

 java.lang.IllegalArgumentException: image == null! 

在这一点上,我很难过。 我希望一双新鲜的眼睛可以帮助我。 有没有人有任何想法?

正如评论中已经提到的那样,你的问题有点不合理,但我会试着告诉你它是如何工作的。

首先尝试以下方法:

 @RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { BufferedImage firstImage = ImageIO.read(new File("/bla.jpg")); response.setContentType("image/gif"); // this should happen automatically ImageIO.write(firstImage, "gif", response.getOutputStream()); response.getOutputStream().close(); } 

将一些名为bla.jpg文件放在根目录中,或将路径更改为某个现有图像文件(也可以是GIF)。 确保您至少具有读取权限。

这应该适用于任何情况,无论是jpg还是gif文件。 如果这不起作用,那么Spring配置可能有问题。 你应该排除这一点。

如果这样做,您可以使用您的方法generateAndWriteToImage()来替换ImageIO.read(new File("/bla.jpg")); 。 你应该完成。

现在,我不知道你的generateAndWriteToImage()方法做了什么,但我假设它创建了一个BufferedImage实例,将一些文本写入图像并返回它。 像这样的东西:

 public static BufferedImage generateAndWriteToImage(String string, int x, int y) { BufferedImage image = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); g.setPaintMode(); g.setFont(g.getFont().deriveFont(30f)); g.drawString(string, 100, 100); g.dispose(); return image; } 

如果使用BufferedImage.TYPE_INT_RGB类型创建映像,则不应导致任何问题。

TL; DR你自己已经发现的另一件事是,一点点重构让你能够关闭ImageOutputStream

假设以下方法:

 @RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { Function function = functionService.getFunction(Integer.valueOf(functionId)); ServletOutputStream servOut = response.getOutputStream(); // Uses default values if you pass in nulls. function.getImage(servOut, null, null); servOut.flush(); servOut.close(); } 

而这个方法:

 public void getImage(OutputStream out, String staticContent, String changedContent) throws IOException { String[] data = {"2", "+", "2", "=", "4"}; Logger logger = Logger.getLogger(AddFunction.class); logger.info("Getting the add image."); ImageUtils.writeSequenceToImage(ImageIO.createImageOutputStream(out), data, 5, Constants.IMAGE_HEIGHT / 2); } 

在第二种方法中,您将使用ImageIO.createImageOutputStream(out) (最后一行)创建ImageOutputStream的本地实例。

我想主要的问题是,你没有关闭这个ImageOutputStream ,这可能导致数据没有被写入任何其他OutputStream (因为缓冲)。

为了使它工作,你可以重构你的方法:

 @RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { Function function = functionService.getFunction(Integer.valueOf(functionId)); ImageOutputStream servOut = ImageIO.createImageOutputStream(response.getOutputStream()); // Uses default values if you pass in nulls. function.getImage(servOut, null, null); servOut.close(); } 

和这个:

 public void getImage(ImageOutputStream out, String staticContent, String changedContent) throws IOException { String[] data = {"2", "+", "2", "=", "4"}; Logger logger = Logger.getLogger(AddFunction.class); logger.info("Getting the add image."); ImageUtils.writeSequenceToImage(out, data, 5, Constants.IMAGE_HEIGHT / 2); } 

这里适用于generateAndWriteToImage()方法。 如果它正确返回BufferedImage的实例,这应该工作(使用重构)。

我没有尝试使用spring而是尝试使用J2EE。 以下方法适合我!

 private void process(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { String baseServletPath = httpServletRequest.getServletContext().getRealPath("/"); System.out.println("Base Servlet Path :"+baseServletPath); String relativeInputFilePath = "images/Tulips.gif"; String imageFilePath = baseServletPath + relativeInputFilePath; File file = new File(imageFilePath); BufferedImage bufferedImage = null; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { bufferedImage = ImageIO.read(file); ImageIO.write(bufferedImage, "GIF", byteArrayOutputStream); } catch (IOException e) { e.printStackTrace(); } byte[] imageData = byteArrayOutputStream.toByteArray(); String relativeOutFilePath = "images/TulipsOut.gif"; String imageOutFilePath = baseServletPath + relativeOutFilePath; File fileOut = new File(imageOutFilePath); FileOutputStream fileOutputStream=null; try { fileOutputStream = new FileOutputStream(fileOut); fileOutputStream.write(imageData); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } try { httpServletResponse.getOutputStream().write( imageData ); } catch (IOException e) { e.printStackTrace(); } }