【发布时间】:2015-02-27 16:08:34
【问题描述】:
我正在编写一个动态生成 GIF 文件的端点。我会从头开始。
我有一个名为Function 的类,它像一个抽象类一样工作,我有几个类,在这个例子中是AddFunction,它们代表一小块功能。在这种情况下,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();
}
首先,Function 通过其 ID 找到。我已经检查过了,并且正在找到正确的功能。这段代码需要一些验证(例如检查传入的 id 是一个有效数字),但我稍后会谈到。然后我抓取 servlet 输出流并将其传递给函数的 getImage 方法。此方法生成描述函数的 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 类的代码可以在 here 找到。我所做的是构建字符串,因此在我们的示例中,日志将输出:
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 引用它时:
<div class="panel columns large-12" ng-show="selectedFunction">
<h2>{{selectedFunction.name}}</h2>
<p>{{selectedFunction.description}}</p>
<p>This function expects a {{selectedFunction.expectedParameterType}} as a parameter.</p>
<p>This function will return a {{selectedFunction.expectedReturnType}}</p>
<img src="/autoalgorithm/functions/function/{{selectedFunction.id}}/image.gif" alt="{{selectedFunction.name}}"/>
</div>
我在 Chrome 中看到以下数据返回:
我在我的页面上看不到任何图像:
我的尝试
我试图查看返回的内容的大小。为此,我将ServletOutputStream 替换为ByteArrayOutputStream,以便获取数据的大小。如果我这样做,我的代码如下所示:
/**
* 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 听起来很合适。然而,当我查看谷歌的回复时,类似的故事:
虽然这次有内容长度,但是没有预览可用,图像仍然没有出现。
我想知道这里是否有人解决了类似的问题?我怀疑这与 GIF 的编码有关,但 ImageIO 不支持从一个流到另一个流的转换,只支持从一个 BufferedImage 类型到另一个。所以我使用ImageIO.read 方法将其读入BufferedImage 并使用ImageIO.write 将其作为gif 写入ServletOutputStream。这产生了以下错误:
java.lang.IllegalArgumentException: image == null!
在这一点上,我很难过。我希望有一双新的眼睛可以帮助我。有人有什么想法吗?
【问题讨论】:
-
如果你把字节数组写成gif文件,你能打开它并正确渲染吗?另外,为什么内容类型是 image/webp?不应该是在Controller方法中注解的image/gif?
-
它应该是。如果没有返回数据,它会以
text/plain出现,这更奇怪!我现在做一个测试,然后为你找出答案。 -
你一次尝试的太多了。这使得解决问题变得困难。你可能有几个。创建一个将 GIF 保存在文件中的测试(与 Spring MVC 框架分开)并验证它是否有效。创建一个更简单的控制器方法,从磁盘读取工作中的 GIF 并将其作为响应发送。
-
我可以发现至少两个问题:(1)您没有关闭 ImageOutputStream 实例。因此,部分图像可能会丢失。 (2) 在
getImage方法的第一个版本中,您同时使用HttpServletResponse和produces。如果我没记错的话,它们不会混合。如果使用HttpServletResponse,则必须在响应中设置内容类型和内容长度。 -
如果你们能为 A)生成 GIF 和 B)在 HTTP 响应中编写它提供一套清晰的步骤,那么您将获得赏金。我在 Stack Overflow 上的任何地方都没有找到这个,所以我认为我们把它记下来很重要。
标签: java image spring-mvc servlets gif