我正在编写一个动态生成GIF文件的端点。我会从头开始。
我有一个名为Function
的类,它的工作方式类似于抽象类,我有几个类,在本例中为AddFunction
,它们代表了一小部分功能。在这种情况下,AddFunction
会将一些数字加在一起。当点击结束点时,AddFunction
的ID被传递给它(它可以是任何,在这个例子中它是添加功能)。控制器中的代码如下:
/**
* 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
对象包装它。这是在我自己的writeSequenceToImage
类中传递到ImageUtils
方法时。最后两个值是写入位置的坐标。在这种情况下,图像的垂直中间位于最左侧。 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。我所做的是构建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引用它时:
<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!
此时,我很难过。我希望一双新鲜的眼睛可以帮助我。有没有人有任何想法?
答案 0 :(得分:3)
正如评论中已经提到的那样,你的问题有点不合理,但我会试着告诉你它是如何运作的。
首先尝试以下操作:
@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);
}
在第二种方法中,您正在使用ImageOutputStream
(最后一行)创建ImageIO.createImageOutputStream(out)
的本地实例。
我想主要的问题是,你没有关闭这个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
的实例,这应该有效(使用重构)。
答案 1 :(得分:0)
我没有尝试使用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();
}
}