Index: trunk/imgserv/imgserv-server/mkdist.sh |
— | — | @@ -46,9 +46,14 @@ |
47 | 47 | permission java.io.FilePermission "\${here}/lib/-", "read"; |
48 | 48 | }; |
49 | 49 | EOF |
50 | | -java -Djava.security.manager -Djava.security.policy=imgserv.policy -jar imgserv-server.jar "\$@" |
| 50 | +exec java -Djava.security.manager -Djava.security.policy=imgserv.policy -jar imgserv-server.jar "\$@" |
51 | 51 | __EOF__ |
52 | 52 | |
| 53 | +cat >bin/run.sh <<__EOF__ |
| 54 | +#! /bin/sh |
| 55 | +exec nohup bin/start.sh "\$@" & |
| 56 | +__EOF__ |
| 57 | + |
53 | 58 | chmod 755 bin/start.sh |
54 | 59 | cd .. |
55 | 60 | tar cf imgserv-server-$vers.tar imgserv-server-$vers |
Index: trunk/imgserv/imgserv-server/lib/pngds.jar |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/imgserv/imgserv-server/lib/pngds.jar |
___________________________________________________________________ |
Added: svn:mime-type |
56 | 61 | + application/octet-stream |
Index: trunk/imgserv/imgserv-server/src/imgservserver/SVGImageHandler.java |
— | — | @@ -0,0 +1,46 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.awt.image.BufferedImage; |
| 15 | +import java.awt.image.RenderedImage; |
| 16 | +import java.io.ByteArrayInputStream; |
| 17 | +import java.io.OutputStream; |
| 18 | +import org.apache.batik.transcoder.TranscoderException; |
| 19 | + |
| 20 | +public class SVGImageHandler implements MultiFormatImageReader { |
| 21 | + int width = 0, height = 0; |
| 22 | + |
| 23 | + public SVGImageHandler(String format) { |
| 24 | + } |
| 25 | + |
| 26 | + public void setSizeHint(int width, int height) { |
| 27 | + this.width = width; |
| 28 | + this.height = height; |
| 29 | + } |
| 30 | + |
| 31 | + public RenderedImage readImage(byte[] data) |
| 32 | + throws ImageTranscoderException { |
| 33 | + SVGRasterizer r = new SVGRasterizer(new ByteArrayInputStream(data)); |
| 34 | + |
| 35 | + if (height > 0) |
| 36 | + r.setImageHeight(height); |
| 37 | + if (width > 0) |
| 38 | + r.setImageWidth(width); |
| 39 | + |
| 40 | + try { |
| 41 | + return r.createBufferedImage(); |
| 42 | + } catch (TranscoderException e) { |
| 43 | + throw new ImageTranscoderException( |
| 44 | + "Cannot rasterize SVG image", e); |
| 45 | + } |
| 46 | + } |
| 47 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageClient.java |
— | — | @@ -10,37 +10,18 @@ |
11 | 11 | |
12 | 12 | package imgservserver; |
13 | 13 | |
14 | | -import java.awt.Graphics2D; |
15 | | -import java.awt.geom.AffineTransform; |
16 | | -import java.awt.image.BufferedImage; |
17 | | -import java.io.BufferedInputStream; |
18 | | -import java.io.ByteArrayInputStream; |
19 | | -import java.io.ByteArrayOutputStream; |
20 | 14 | import java.io.File; |
21 | 15 | import java.io.FileInputStream; |
22 | 16 | import java.io.FileOutputStream; |
23 | 17 | import java.io.IOException; |
24 | 18 | import java.io.OutputStream; |
25 | 19 | import java.net.Socket; |
26 | | -import java.util.Iterator; |
27 | | -import javax.imageio.ImageIO; |
28 | | -import javax.imageio.ImageReader; |
29 | | -import javax.imageio.ImageWriter; |
30 | | -import javax.imageio.stream.MemoryCacheImageInputStream; |
31 | | -import javax.imageio.stream.MemoryCacheImageOutputStream; |
32 | | -import org.apache.batik.transcoder.Transcoder; |
33 | | -import org.apache.batik.transcoder.TranscoderException; |
34 | | -import org.apache.batik.transcoder.TranscoderInput; |
35 | | -import org.apache.batik.transcoder.TranscoderOutput; |
36 | | -import org.apache.batik.transcoder.TranscodingHints; |
37 | | -import org.apache.batik.transcoder.image.JPEGTranscoder; |
38 | | -import org.apache.batik.transcoder.image.PNGTranscoder; |
39 | | -import org.apache.batik.transcoder.image.TIFFTranscoder; |
40 | 20 | import pngds.PNGResizer; |
41 | 21 | |
42 | 22 | public class ImageClient extends Thread { |
43 | 23 | Socket client; |
44 | | - BufferedInputStream reader; |
| 24 | + ImageClientInputStream reader; |
| 25 | + ImageClientOutputStream writer; |
45 | 26 | Configuration config; |
46 | 27 | int pngcount = 0; |
47 | 28 | |
— | — | @@ -52,7 +33,7 @@ |
53 | 34 | handleRequest(); |
54 | 35 | } catch (Exception e) { |
55 | 36 | System.out.printf("%% Error occurred handling client request: %s\n", |
56 | | - e.getMessage()); |
| 37 | + e.toString()); |
57 | 38 | return; |
58 | 39 | } finally { |
59 | 40 | try { |
— | — | @@ -62,7 +43,8 @@ |
63 | 44 | } |
64 | 45 | |
65 | 46 | private void handleRequest() throws IOException { |
66 | | - reader = new BufferedInputStream(client.getInputStream()); |
| 47 | + reader = new ImageClientInputStream(client.getInputStream()); |
| 48 | + writer = new ImageClientOutputStream(client); |
67 | 49 | |
68 | 50 | /* |
69 | 51 | * The protocol request format is like this: |
— | — | @@ -78,8 +60,8 @@ |
79 | 61 | * |
80 | 62 | * A successful reply looks like this: |
81 | 63 | * |
82 | | - * OK 2345 |
83 | | - * <2345 bytes of data> |
| 64 | + * OK |
| 65 | + * hhhh<hhhh bytes of data>hhhh<hhhh bytes of data>[etc] |
84 | 66 | */ |
85 | 67 | |
86 | 68 | String informat = null, outformat = null; |
— | — | @@ -87,7 +69,7 @@ |
88 | 70 | int width = -1, height = -1; |
89 | 71 | |
90 | 72 | for (;;) { |
91 | | - String line = readLine(); |
| 73 | + String line = reader.readLine(); |
92 | 74 | String[] args = line.split(" "); |
93 | 75 | |
94 | 76 | if (args.length < 2) { |
— | — | @@ -151,38 +133,40 @@ |
152 | 134 | break; |
153 | 135 | } |
154 | 136 | |
155 | | - byte[] out; |
| 137 | + writer.setChunked(true); |
| 138 | + writer.setHeader("OK\r\n"); |
| 139 | + |
| 140 | + ImageTranscoder tr = new ImageTranscoder(); |
156 | 141 | |
157 | | - if (informat.equals("svg")) { |
158 | | - out = transcodeSVG(informat, outformat, width, height, data); |
159 | | - } else { |
160 | | - out = transcodeRaster(informat, outformat, width, height, data); |
| 142 | + try { |
| 143 | + /* |
| 144 | + if (informat.equals("svg")) { |
| 145 | + tr.transcodeSVG(informat, outformat, width, height, data, writer); |
| 146 | + } else { |
| 147 | + tr.transcodeRaster(informat, outformat, width, height, data, writer); |
| 148 | + } |
| 149 | + */ |
| 150 | + tr.transcode(informat, outformat, width, height, data, writer); |
| 151 | + } catch (ImageTranscoderException e) { |
| 152 | + String error = e.getMessage(); |
| 153 | + Throwable cause = e; |
| 154 | + while ((cause = cause.getCause()) != null) { |
| 155 | + error = error + ": " + cause.getMessage(); |
| 156 | + } |
| 157 | + |
| 158 | + writer.cancel(); |
| 159 | + String status = "ERROR " + error + "\r\n"; |
| 160 | + writer.write(status.getBytes()); |
| 161 | + |
| 162 | + System.err.printf("%% [client: %s] %s\n", client.getRemoteSocketAddress().toString(), |
| 163 | + error); |
161 | 164 | } |
162 | 165 | |
163 | | - String status = "OK " + out.length + "\r\n"; |
164 | | - OutputStream clout = client.getOutputStream(); |
165 | | - clout.write(status.getBytes()); |
166 | | - clout.write(out); |
| 166 | + writer.close(); |
167 | 167 | reader.close(); |
168 | 168 | client.close(); |
169 | 169 | } |
170 | 170 | |
171 | | - private String readLine() throws IOException { |
172 | | - StringBuilder b = new StringBuilder(); |
173 | | - int i; |
174 | | - |
175 | | - while ((i = reader.read()) != -1) { |
176 | | - char c = (char) i; |
177 | | - if (c == '\r') |
178 | | - continue; |
179 | | - if (c == '\n') |
180 | | - return b.toString(); |
181 | | - b.append(c); |
182 | | - } |
183 | | - |
184 | | - throw new IOException("Unexpected end of stream looking for \\r\\n"); |
185 | | - } |
186 | | - |
187 | 171 | private int transcodepngds(int len, int width, int height) |
188 | 172 | throws IOException { |
189 | 173 | String inp = config.getTmpdir() + "/pngds_" + Thread.currentThread().getId() |
— | — | @@ -242,134 +226,4 @@ |
243 | 227 | |
244 | 228 | return ret; |
245 | 229 | } |
246 | | - |
247 | | - private byte[] transcodeRaster(String informat, String outformat, |
248 | | - int width, int height, |
249 | | - byte[] data) throws IOException { |
250 | | - ByteArrayInputStream bis = new ByteArrayInputStream(data); |
251 | | - Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(informat); |
252 | | - if (!readers.hasNext()) { |
253 | | - System.out.printf("%% No reader found for format \"%s\".\n", |
254 | | - informat); |
255 | | - return null; |
256 | | - } |
257 | | - |
258 | | - ImageReader imgr = readers.next(); |
259 | | - imgr.setInput(new MemoryCacheImageInputStream( |
260 | | - new ByteArrayInputStream(data))); |
261 | | - BufferedImage img = imgr.read(0), dest; |
262 | | - |
263 | | - if (width != -1 || height != -1) { |
264 | | - if (width == -1) |
265 | | - width = (int) (img.getWidth() * ((double)height / img.getHeight())); |
266 | | - if (height == -1) |
267 | | - height = (int) (img.getHeight() * ((double)width / img.getWidth())); |
268 | | - dest = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
269 | | - Graphics2D g = dest.createGraphics(); |
270 | | - AffineTransform at = AffineTransform.getScaleInstance( |
271 | | - (double)width/img.getWidth(), |
272 | | - (double)height/img.getHeight()); |
273 | | - g.drawRenderedImage(img, at); |
274 | | - } else { |
275 | | - dest = img; |
276 | | - } |
277 | | - |
278 | | - Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(outformat); |
279 | | - if (!writers.hasNext()) { |
280 | | - System.out.printf("%% No writer found for format \"%s\".\n", |
281 | | - outformat); |
282 | | - return null; |
283 | | - } |
284 | | - |
285 | | - ByteArrayOutputStream outs = new ByteArrayOutputStream(); |
286 | | - ImageWriter imgw = writers.next(); |
287 | | - imgw.setOutput(new MemoryCacheImageOutputStream(outs)); |
288 | | - imgw.write(dest); |
289 | | - |
290 | | - byte[] out = outs.toByteArray(); |
291 | | - return out; |
292 | | - } |
293 | | - |
294 | | - static class NamedTranscoder { |
295 | | - Class clas; |
296 | | - String name; |
297 | | - |
298 | | - public NamedTranscoder(String name, Class clas) { |
299 | | - this.name = name; |
300 | | - this.clas = clas; |
301 | | - } |
302 | | - |
303 | | - public Transcoder getInstance() |
304 | | - throws InstantiationException, IllegalAccessException { |
305 | | - return (Transcoder) clas.newInstance(); |
306 | | - } |
307 | | - |
308 | | - public String getName() { |
309 | | - return name; |
310 | | - } |
311 | | - } |
312 | | - |
313 | | - static final NamedTranscoder[] namedTranscoders = { |
314 | | - new NamedTranscoder("jpeg", JPEGTranscoder.class), |
315 | | - new NamedTranscoder("png", PNGTranscoder.class), |
316 | | - new NamedTranscoder("tiff", TIFFTranscoder.class), |
317 | | - }; |
318 | | - |
319 | | - private byte[] transcodeSVG(String informat, String outformat, |
320 | | - int width, int height, |
321 | | - byte[] data) throws IOException { |
322 | | - Transcoder coder = null; |
323 | | - |
324 | | - for (int i = 0; i < namedTranscoders.length; ++i) { |
325 | | - if (namedTranscoders[i].getName().equals(outformat)) { |
326 | | - try { |
327 | | - coder = namedTranscoders[i].getInstance(); |
328 | | - } catch (Exception e) { |
329 | | - System.out.printf("%% Exception trying to instantiate transcoder \"%s\": %s.\n", |
330 | | - namedTranscoders[i].getName(), e.getMessage()); |
331 | | - return null; |
332 | | - } |
333 | | - break; |
334 | | - } |
335 | | - } |
336 | | - |
337 | | - if (coder == null) { |
338 | | - System.out.printf("%% No SVG transcoder found for format \"%s\".\n", |
339 | | - outformat); |
340 | | - return null; |
341 | | - } |
342 | | - |
343 | | - if (coder instanceof JPEGTranscoder) |
344 | | - ((JPEGTranscoder) coder).addTranscodingHint(JPEGTranscoder.KEY_QUALITY, |
345 | | - new Float(.8)); |
346 | | - |
347 | | - TranscodingHints.Key kwidth, kheight; |
348 | | - try { |
349 | | - kwidth = (TranscodingHints.Key) |
350 | | - coder.getClass().getField("KEY_WIDTH").get(null); |
351 | | - kheight = (TranscodingHints.Key) |
352 | | - coder.getClass().getField("KEY_WIDTH").get(null); |
353 | | - } catch (Exception e) { |
354 | | - System.out.printf("%% Couldn't extract width or height keys from transcoder.\n"); |
355 | | - return null; |
356 | | - } |
357 | | - |
358 | | - if (width > 0) |
359 | | - coder.addTranscodingHint(kwidth, new Float(width)); |
360 | | - if (height > 0) |
361 | | - coder.addTranscodingHint(kheight, new Float(height)); |
362 | | - |
363 | | - ByteArrayOutputStream out = new ByteArrayOutputStream(); |
364 | | - TranscoderInput input = new TranscoderInput(new ByteArrayInputStream(data)); |
365 | | - TranscoderOutput output = new TranscoderOutput(out); |
366 | | - |
367 | | - try { |
368 | | - coder.transcode(input, output); |
369 | | - } catch (Exception e) { |
370 | | - System.out.printf("%% Exception transcoding SVG image: %s\n", |
371 | | - e.toString()); |
372 | | - return null; |
373 | | - } |
374 | | - return out.toByteArray(); |
375 | | - } |
376 | 230 | } |
Index: trunk/imgserv/imgserv-server/src/imgservserver/MultiFormatImageWriter.java |
— | — | @@ -0,0 +1,20 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.awt.image.RenderedImage; |
| 15 | +import java.io.OutputStream; |
| 16 | + |
| 17 | +public interface MultiFormatImageWriter { |
| 18 | + public void writeImage(RenderedImage img, OutputStream out) throws ImageTranscoderException; |
| 19 | + public int getPreferredImageType(); |
| 20 | + public boolean hasAlpha(); |
| 21 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageTranscoderException.java |
— | — | @@ -0,0 +1,21 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +public class ImageTranscoderException extends Exception { |
| 15 | + public ImageTranscoderException(String message) { |
| 16 | + super(message); |
| 17 | + } |
| 18 | + |
| 19 | + public ImageTranscoderException(String message, Exception nested) { |
| 20 | + super(message, nested); |
| 21 | + } |
| 22 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/Configuration.java |
— | — | @@ -0,0 +1,67 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.net.InetAddress; |
| 15 | +import java.util.Properties; |
| 16 | + |
| 17 | +public class Configuration { |
| 18 | + static final int DEFAULT_PORT = 8765; |
| 19 | + |
| 20 | + boolean usepngds = false; |
| 21 | + String tmpdir = null; |
| 22 | + int port = DEFAULT_PORT; |
| 23 | + InetAddress address; |
| 24 | + |
| 25 | + public Configuration(Properties p) { |
| 26 | + String x; |
| 27 | + |
| 28 | + if ((x = p.getProperty("tmpdir")) != null) |
| 29 | + tmpdir = x; |
| 30 | + |
| 31 | + if ((x = p.getProperty("usepngds")) != null) |
| 32 | + usepngds = x.equals("true"); |
| 33 | + |
| 34 | + if ((x = p.getProperty("port")) != null) |
| 35 | + port = Integer.parseInt(x); |
| 36 | + |
| 37 | + if ((x = p.getProperty("bind")) != null) { |
| 38 | + try { |
| 39 | + address = InetAddress.getByName(x); |
| 40 | + } catch (Exception e) { |
| 41 | + System.err.printf("%% Invalid bind address \"%s\": %s.\n", |
| 42 | + x); |
| 43 | + System.exit(1); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + if (tmpdir == null && usepngds) { |
| 48 | + System.err.printf("%% tmpdir must be set when using pngds.\n"); |
| 49 | + System.exit(1); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + public boolean getUsepngds() { |
| 54 | + return usepngds; |
| 55 | + } |
| 56 | + |
| 57 | + public int getPort() { |
| 58 | + return port; |
| 59 | + } |
| 60 | + |
| 61 | + public String getTmpdir() { |
| 62 | + return tmpdir; |
| 63 | + } |
| 64 | + |
| 65 | + public InetAddress getBindAddress() { |
| 66 | + return address; |
| 67 | + } |
| 68 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageTranscoder.java |
— | — | @@ -0,0 +1,80 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* @(#) $Id$ */ |
| 10 | +package imgservserver; |
| 11 | + |
| 12 | +import java.awt.Color; |
| 13 | +import java.awt.Graphics2D; |
| 14 | +import java.awt.Transparency; |
| 15 | +import java.awt.geom.AffineTransform; |
| 16 | +import java.awt.image.BufferedImage; |
| 17 | +import java.awt.image.RenderedImage; |
| 18 | +import java.io.OutputStream; |
| 19 | + |
| 20 | +public class ImageTranscoder { |
| 21 | + public void transcode(String informat, String outformat, |
| 22 | + int width, int height, |
| 23 | + byte[] data, OutputStream out) |
| 24 | + throws ImageTranscoderException { |
| 25 | + MultiFormatImageReader reader = MultiFormatImageFactory.getReader(informat); |
| 26 | + MultiFormatImageWriter writer = MultiFormatImageFactory.getWriter(outformat); |
| 27 | + |
| 28 | + reader.setSizeHint(width, height); |
| 29 | + RenderedImage img = reader.readImage(data); |
| 30 | + RenderedImage result; |
| 31 | + |
| 32 | + /* |
| 33 | + * Some readers (e.g. SVG) can render the input at the size we want if |
| 34 | + * given a size hint. If so, we don't need to scale the image here. |
| 35 | + */ |
| 36 | + if ((width != -1 && img.getWidth() != width) |
| 37 | + || (height != -1 && img.getHeight() != height)) |
| 38 | + |
| 39 | + { |
| 40 | + BufferedImage dest; |
| 41 | + |
| 42 | + if (width == -1) |
| 43 | + width = (int) (img.getWidth() * ((double) height / img.getHeight())); |
| 44 | + |
| 45 | + if (height == -1) |
| 46 | + height = (int) (img.getHeight() * ((double) width / img.getWidth())); |
| 47 | + |
| 48 | + dest = new BufferedImage(width, height, writer.getPreferredImageType()); |
| 49 | + Graphics2D g = dest.createGraphics(); |
| 50 | + AffineTransform at = AffineTransform.getScaleInstance( |
| 51 | + (double) width / img.getWidth(), |
| 52 | + (double) height / img.getHeight()); |
| 53 | + g.drawRenderedImage(img, at); |
| 54 | + result = dest; |
| 55 | + } else { |
| 56 | + /* |
| 57 | + * If we don't scale the image, there's no chance to convert the image |
| 58 | + * to the preferred image type of the output format. So we remove |
| 59 | + * any alpha channel here if it's needed. |
| 60 | + */ |
| 61 | + if (!writer.hasAlpha() && img.getColorModel().getTransparency() != Transparency.OPAQUE) |
| 62 | + result = removeAlpha(img); |
| 63 | + else |
| 64 | + result = img; |
| 65 | + } |
| 66 | + |
| 67 | + writer.writeImage(result, out); |
| 68 | + } |
| 69 | + |
| 70 | + public RenderedImage removeAlpha(RenderedImage img) { |
| 71 | + int w = img.getWidth(); |
| 72 | + int h = img.getHeight(); |
| 73 | + BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); |
| 74 | + Graphics2D g = ret.createGraphics(); |
| 75 | + g.setColor(Color.WHITE); |
| 76 | + g.fillRect(0,0,w,h); |
| 77 | + g.drawRenderedImage(img, null); |
| 78 | + g.dispose(); |
| 79 | + return ret; |
| 80 | + } |
| 81 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/MultiFormatImageReader.java |
— | — | @@ -0,0 +1,18 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.awt.image.RenderedImage; |
| 15 | + |
| 16 | +public interface MultiFormatImageReader { |
| 17 | + public void setSizeHint(int width, int height); |
| 18 | + public RenderedImage readImage(byte[] data) throws ImageTranscoderException; |
| 19 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/TIFFImageHandler.java |
— | — | @@ -0,0 +1,65 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.awt.image.BufferedImage; |
| 15 | +import java.awt.image.RenderedImage; |
| 16 | +import java.io.ByteArrayInputStream; |
| 17 | +import java.io.OutputStream; |
| 18 | +import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam; |
| 19 | +import org.apache.batik.ext.awt.image.codec.tiff.TIFFEncodeParam; |
| 20 | +import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageDecoder; |
| 21 | +import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageEncoder; |
| 22 | +import org.apache.batik.ext.awt.image.codec.util.MemoryCacheSeekableStream; |
| 23 | + |
| 24 | +public class TIFFImageHandler |
| 25 | +implements MultiFormatImageReader, MultiFormatImageWriter { |
| 26 | + |
| 27 | + public TIFFImageHandler(String format) { |
| 28 | + } |
| 29 | + |
| 30 | + public void setSizeHint(int w, int h) { |
| 31 | + } |
| 32 | + |
| 33 | + public int getPreferredImageType() { |
| 34 | + return BufferedImage.TYPE_4BYTE_ABGR; |
| 35 | + } |
| 36 | + |
| 37 | + public boolean hasAlpha() { |
| 38 | + return true; |
| 39 | + } |
| 40 | + |
| 41 | + public RenderedImage readImage(byte[] data) |
| 42 | + throws ImageTranscoderException { |
| 43 | + TIFFImageDecoder coder; |
| 44 | + TIFFDecodeParam param = new TIFFDecodeParam(); |
| 45 | + MemoryCacheSeekableStream in = new MemoryCacheSeekableStream( |
| 46 | + new ByteArrayInputStream(data)); |
| 47 | + coder = new TIFFImageDecoder(in, param); |
| 48 | + try { |
| 49 | + return coder.decodeAsRenderedImage(); |
| 50 | + } catch (Exception e) { |
| 51 | + throw new ImageTranscoderException("Cannot decode TIFF data", e); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + public void writeImage(RenderedImage img, OutputStream out) throws ImageTranscoderException { |
| 56 | + TIFFImageEncoder coder; |
| 57 | + TIFFEncodeParam param = new TIFFEncodeParam(); |
| 58 | + param.setCompression(TIFFEncodeParam.COMPRESSION_DEFLATE); |
| 59 | + coder = new TIFFImageEncoder(out, param); |
| 60 | + try { |
| 61 | + coder.encode(img); |
| 62 | + } catch (Exception e) { |
| 63 | + throw new ImageTranscoderException("Cannot encode TIFF data", e); |
| 64 | + } |
| 65 | + } |
| 66 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/SVGRasterizer.java |
— | — | @@ -0,0 +1,234 @@ |
| 2 | +/***************************************************************************** |
| 3 | + * Copyright (C) The Apache Software Foundation. All rights reserved. * |
| 4 | + * ------------------------------------------------------------------------- * |
| 5 | + * This software is published under the terms of the Apache Software License * |
| 6 | + * version 1.1, a copy of which has been included with this distribution in * |
| 7 | + * the LICENSE file. * |
| 8 | + |
| 9 | + *****************************************************************************/ |
| 10 | + |
| 11 | +package imgservserver; |
| 12 | + |
| 13 | +import java.awt.image.BufferedImage; |
| 14 | + |
| 15 | +import java.awt.Paint; |
| 16 | + |
| 17 | +import java.io.File; |
| 18 | +import java.io.FileInputStream; |
| 19 | +import java.io.InputStream; |
| 20 | +import java.io.Reader; |
| 21 | + |
| 22 | +import java.net.URL; |
| 23 | + |
| 24 | +import java.util.Map; |
| 25 | + |
| 26 | +import org.apache.batik.transcoder.TranscoderException; |
| 27 | +import org.apache.batik.transcoder.TranscoderInput; |
| 28 | +import org.apache.batik.transcoder.TranscoderOutput; |
| 29 | +import org.apache.batik.transcoder.TranscodingHints; |
| 30 | + |
| 31 | +import org.apache.batik.transcoder.image.ImageTranscoder; |
| 32 | + |
| 33 | +import org.w3c.dom.svg.SVGDocument; |
| 34 | + |
| 35 | +/** |
| 36 | + * This class provides a simple and method based API for converting a SVG |
| 37 | + * document fragment to a <tt>BufferedImage</tt>. |
| 38 | + * |
| 39 | + * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a> |
| 40 | + */ |
| 41 | +public class SVGRasterizer { |
| 42 | + |
| 43 | + /** |
| 44 | + * The transcoder input. |
| 45 | + */ |
| 46 | + protected TranscoderInput input; |
| 47 | + |
| 48 | + /** |
| 49 | + * The transcoder hints. |
| 50 | + */ |
| 51 | + protected TranscodingHints hints = new TranscodingHints(); |
| 52 | + |
| 53 | + /** |
| 54 | + * The image that represents the SVG document. |
| 55 | + */ |
| 56 | + protected BufferedImage img; |
| 57 | + |
| 58 | + /** |
| 59 | + * Constructs a new SVGRasterizer. |
| 60 | + * |
| 61 | + * @param uri the uri of the document to rasterize |
| 62 | + */ |
| 63 | + public SVGRasterizer(String uri) { |
| 64 | + this.input = new TranscoderInput(uri); |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * Constructs a new SVGRasterizer. |
| 69 | + * |
| 70 | + * @param url the URL of the document to rasterize |
| 71 | + */ |
| 72 | + public SVGRasterizer(URL url) { |
| 73 | + this.input = new TranscoderInput(url.toString()); |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * Constructs a new SVGRasterizer converter. |
| 78 | + * |
| 79 | + * @param istream the input stream that represents the SVG document to |
| 80 | + * rasterize |
| 81 | + */ |
| 82 | + public SVGRasterizer(InputStream istream) { |
| 83 | + this.input = new TranscoderInput(istream); |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * Constructs a new SVGRasterizer converter. |
| 88 | + * |
| 89 | + * @param reader the reader that represents the SVG document to rasterize |
| 90 | + */ |
| 91 | + public SVGRasterizer(Reader reader) { |
| 92 | + this.input = new TranscoderInput(reader); |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Constructs a new SVGRasterizer converter. |
| 97 | + * |
| 98 | + * @param document the SVG document to rasterize |
| 99 | + */ |
| 100 | + public SVGRasterizer(SVGDocument document) { |
| 101 | + this.input = new TranscoderInput(document); |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Returns the image that represents the SVG document. |
| 106 | + */ |
| 107 | + public BufferedImage createBufferedImage() throws TranscoderException { |
| 108 | + Rasterizer r = new Rasterizer(); |
| 109 | + r.setTranscodingHints((Map)hints); |
| 110 | + r.transcode(input, null); |
| 111 | + return img; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Sets the width of the image to rasterize. |
| 116 | + * |
| 117 | + * @param width the image width |
| 118 | + */ |
| 119 | + public void setImageWidth(float width) { |
| 120 | + hints.put(ImageTranscoder.KEY_WIDTH, new Float(width)); |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * Sets the height of the image to rasterize. |
| 125 | + * |
| 126 | + * @param width the image height |
| 127 | + */ |
| 128 | + public void setImageHeight(float height) { |
| 129 | + hints.put(ImageTranscoder.KEY_HEIGHT, new Float(height)); |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Sets the preferred language to use. SVG documents can provide text in |
| 134 | + * multiple languages, this method lets you control which language to use |
| 135 | + * if possible. e.g. "en" for english or "fr" for french. |
| 136 | + * |
| 137 | + * @param language the preferred language to use |
| 138 | + */ |
| 139 | + public void setLanguages(String language) { |
| 140 | + hints.put(ImageTranscoder.KEY_LANGUAGE, language); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Sets the unit conversion factor to the specified value. This method |
| 145 | + * lets you choose how units such as 'em' are converted. e.g. 0.26458 is |
| 146 | + * 96dpi (the default) or 0.3528 is 72dpi. |
| 147 | + * |
| 148 | + * @param px2mm the pixel to millimeter convertion factor. |
| 149 | + */ |
| 150 | + public void setPixelToMMFactor(float px2mm) { |
| 151 | + hints.put(ImageTranscoder.KEY_PIXEL_TO_MM, new Float(px2mm)); |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Sets the uri of the user stylesheet. The user stylesheet can be used to |
| 156 | + * override styles. |
| 157 | + * |
| 158 | + * @param uri the uri of the user stylesheet |
| 159 | + */ |
| 160 | + public void setUserStyleSheetURI(String uri) { |
| 161 | + hints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, uri); |
| 162 | + } |
| 163 | + |
| 164 | + /** |
| 165 | + * Sets whether or not the XML parser used to parse SVG document should be |
| 166 | + * validating or not, depending on the specified parameter. For futher |
| 167 | + * details about how media work, see the |
| 168 | + * <a href="http://www.w3.org/TR/CSS2/media.html">Media types in the CSS2 |
| 169 | + * specification</a>. |
| 170 | + * |
| 171 | + * @param b true means the XML parser will validate its input |
| 172 | + */ |
| 173 | + public void setXMLParserValidating(boolean b) { |
| 174 | + hints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, |
| 175 | + (b ? Boolean.TRUE : Boolean.FALSE)); |
| 176 | + } |
| 177 | + |
| 178 | + /** |
| 179 | + * Sets the media to rasterize. The medium should be separated by |
| 180 | + * comma. e.g. "screen", "print" or "screen, print" |
| 181 | + * |
| 182 | + * @param media the media to use |
| 183 | + */ |
| 184 | + public void setMedia(String media) { |
| 185 | + hints.put(ImageTranscoder.KEY_MEDIA, media); |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * Sets the alternate stylesheet to use. For futher details, you can have |
| 190 | + * a look at the <a href="http://www.w3.org/TR/xml-stylesheet/">Associating |
| 191 | + * Style Sheets with XML documents</a>. |
| 192 | + * |
| 193 | + * @param alternateStylesheet the alternate stylesheet to use if possible |
| 194 | + */ |
| 195 | + public void setAlternateStylesheet(String alternateStylesheet) { |
| 196 | + hints.put(ImageTranscoder.KEY_ALTERNATE_STYLESHEET, |
| 197 | + alternateStylesheet); |
| 198 | + } |
| 199 | + |
| 200 | + /** |
| 201 | + * Sets the Paint to use for the background of the image. |
| 202 | + * |
| 203 | + * @param p the paint to use for the background |
| 204 | + */ |
| 205 | + public void setBackgroundColor(Paint p) { |
| 206 | + hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, p); |
| 207 | + } |
| 208 | + |
| 209 | + /** |
| 210 | + * An image transcoder that stores the resulting image. |
| 211 | + */ |
| 212 | + protected class Rasterizer extends ImageTranscoder { |
| 213 | + |
| 214 | + public BufferedImage createImage(int w, int h) { |
| 215 | + return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); |
| 216 | + } |
| 217 | + |
| 218 | + public void writeImage(BufferedImage img, TranscoderOutput output) |
| 219 | + throws TranscoderException { |
| 220 | + SVGRasterizer.this.img = img; |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | +/* // debug |
| 225 | + public static void main(String [] args) throws Exception { |
| 226 | + SVGRasterizer r = new SVGRasterizer(new File(args[0]).toURL()); |
| 227 | + r.setBackgroundColor(java.awt.Color.white); |
| 228 | + BufferedImage img = r.createBufferedImage(); |
| 229 | + javax.swing.JFrame f = new javax.swing.JFrame(); |
| 230 | + f.getContentPane().add(new javax.swing.JLabel(new javax.swing.ImageIcon(img)), java.awt.BorderLayout.CENTER); |
| 231 | + f.pack(); |
| 232 | + f.show(); |
| 233 | + }*/ |
| 234 | +} |
| 235 | + |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageIOImageHandler.java |
— | — | @@ -0,0 +1,95 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.awt.image.BufferedImage; |
| 15 | +import java.awt.image.RenderedImage; |
| 16 | +import java.io.ByteArrayInputStream; |
| 17 | +import java.io.InputStream; |
| 18 | +import java.io.OutputStream; |
| 19 | +import java.util.Iterator; |
| 20 | +import javax.imageio.ImageIO; |
| 21 | +import javax.imageio.ImageReader; |
| 22 | +import javax.imageio.ImageWriter; |
| 23 | +import javax.imageio.stream.MemoryCacheImageInputStream; |
| 24 | +import javax.imageio.stream.MemoryCacheImageOutputStream; |
| 25 | + |
| 26 | +public class ImageIOImageHandler |
| 27 | +implements MultiFormatImageReader, MultiFormatImageWriter { |
| 28 | + String format; |
| 29 | + boolean hasAlpha = true; |
| 30 | + |
| 31 | + public ImageIOImageHandler(String f) { |
| 32 | + format = f; |
| 33 | + if (format.equals("jpeg")) |
| 34 | + hasAlpha = false; |
| 35 | + } |
| 36 | + |
| 37 | + public boolean hasAlpha() { |
| 38 | + return hasAlpha; |
| 39 | + } |
| 40 | + |
| 41 | + public void setSizeHint(int width, int height) { |
| 42 | + } |
| 43 | + |
| 44 | + public int getPreferredImageType() { |
| 45 | + if (hasAlpha) |
| 46 | + return BufferedImage.TYPE_INT_ARGB; |
| 47 | + else |
| 48 | + return BufferedImage.TYPE_INT_RGB; |
| 49 | + } |
| 50 | + |
| 51 | + public RenderedImage readImage(byte[] data) |
| 52 | + throws ImageTranscoderException { |
| 53 | + ByteArrayInputStream bis = new ByteArrayInputStream(data); |
| 54 | + |
| 55 | + Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format); |
| 56 | + if (!readers.hasNext()) { |
| 57 | + throw new ImageTranscoderException( |
| 58 | + "No reader found for format \"" + format + "\""); |
| 59 | + } |
| 60 | + |
| 61 | + ImageReader imgr = readers.next(); |
| 62 | + imgr.setInput(new MemoryCacheImageInputStream( |
| 63 | + new ByteArrayInputStream(data))); |
| 64 | + |
| 65 | + BufferedImage img; |
| 66 | + |
| 67 | + try { |
| 68 | + img = imgr.read(0); |
| 69 | + return img; |
| 70 | + } catch (Exception e) { |
| 71 | + throw new ImageTranscoderException( |
| 72 | + "Could not read source image", e); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + public void writeImage(RenderedImage image, OutputStream out) |
| 77 | + throws ImageTranscoderException { |
| 78 | + Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format); |
| 79 | + if (!writers.hasNext()) { |
| 80 | + throw new ImageTranscoderException( |
| 81 | + "No writer found for format \"" + format + "\""); |
| 82 | + } |
| 83 | + |
| 84 | + ImageWriter imgw = writers.next(); |
| 85 | + imgw.setOutput(new MemoryCacheImageOutputStream(out)); |
| 86 | + |
| 87 | + try { |
| 88 | + imgw.write(image); |
| 89 | + } catch (Exception e) { |
| 90 | + throw new ImageTranscoderException( |
| 91 | + "Cannot write image to client", e); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + |
| 96 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageClientInputStream.java |
— | — | @@ -0,0 +1,41 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.io.BufferedInputStream; |
| 15 | +import java.io.IOException; |
| 16 | +import java.io.InputStream; |
| 17 | + |
| 18 | +public class ImageClientInputStream extends BufferedInputStream { |
| 19 | + public ImageClientInputStream(InputStream in) { |
| 20 | + super(in); |
| 21 | + } |
| 22 | + |
| 23 | + public ImageClientInputStream(InputStream in, int size) { |
| 24 | + super(in, size); |
| 25 | + } |
| 26 | + |
| 27 | + public String readLine() throws IOException { |
| 28 | + StringBuilder b = new StringBuilder(); |
| 29 | + int i; |
| 30 | + |
| 31 | + while ((i = read()) != -1) { |
| 32 | + char c = (char) i; |
| 33 | + if (c == '\r') |
| 34 | + continue; |
| 35 | + if (c == '\n') |
| 36 | + return b.toString(); |
| 37 | + b.append(c); |
| 38 | + } |
| 39 | + |
| 40 | + throw new IOException("Unexpected end of stream looking for \\r\\n"); |
| 41 | + } |
| 42 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/ImageClientOutputStream.java |
— | — | @@ -0,0 +1,109 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +import java.io.BufferedOutputStream; |
| 15 | +import java.io.IOException; |
| 16 | +import java.io.OutputStream; |
| 17 | +import java.net.Socket; |
| 18 | + |
| 19 | +public class ImageClientOutputStream extends OutputStream { |
| 20 | + static final int BUFFER_SIZE = 8192; |
| 21 | + static final String END_OF_CHUNKED_DATA_MARKER = "0000"; |
| 22 | + |
| 23 | + BufferedOutputStream strm; |
| 24 | + boolean chunked = false; |
| 25 | + byte[] buffer = new byte[BUFFER_SIZE]; |
| 26 | + int bufpos = 0; |
| 27 | + String header = null; |
| 28 | + |
| 29 | + public ImageClientOutputStream(Socket client) throws IOException { |
| 30 | + strm = new BufferedOutputStream(client.getOutputStream()); |
| 31 | + } |
| 32 | + |
| 33 | + public void cancel() throws IOException { |
| 34 | + bufpos = 0; |
| 35 | + chunked = false; |
| 36 | + header = null; |
| 37 | + } |
| 38 | + |
| 39 | + public void setChunked(boolean c) throws IOException { |
| 40 | + flushBuffer(); |
| 41 | + if (chunked) |
| 42 | + strm.write(END_OF_CHUNKED_DATA_MARKER.getBytes()); |
| 43 | + chunked = c; |
| 44 | + } |
| 45 | + |
| 46 | + @Override |
| 47 | + public void close() throws IOException { |
| 48 | + flush(); |
| 49 | + if (chunked) |
| 50 | + strm.write(END_OF_CHUNKED_DATA_MARKER.getBytes()); |
| 51 | + strm.close(); |
| 52 | + } |
| 53 | + |
| 54 | + @Override |
| 55 | + public void flush() throws IOException { |
| 56 | + flushBuffer(); |
| 57 | + strm.flush(); |
| 58 | + } |
| 59 | + |
| 60 | + @Override |
| 61 | + public void write(byte[] b) throws IOException { |
| 62 | + if (b.length == 0) |
| 63 | + return; |
| 64 | + addToBuffer(b, 0, b.length); |
| 65 | + } |
| 66 | + |
| 67 | + @Override |
| 68 | + public void write(byte[] b, int off, int len) throws IOException { |
| 69 | + if (len == 0) |
| 70 | + return; |
| 71 | + |
| 72 | + addToBuffer(b, off, len); |
| 73 | + } |
| 74 | + |
| 75 | + public void write(int b) throws IOException { |
| 76 | + byte[] a = new byte[1]; |
| 77 | + a[0] = (byte) b; |
| 78 | + addToBuffer(a, 0, 1); |
| 79 | + } |
| 80 | + |
| 81 | + private void addToBuffer(byte[] data, int offs, int len) |
| 82 | + throws IOException { |
| 83 | + for (int i = offs; i < len + offs; ++i) { |
| 84 | + buffer[bufpos++] = data[i]; |
| 85 | + if (bufpos == BUFFER_SIZE) |
| 86 | + flushBuffer(); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + public void setHeader(String header) { |
| 91 | + this.header = header; |
| 92 | + } |
| 93 | + |
| 94 | + private void flushBuffer() throws IOException { |
| 95 | + if (header != null) { |
| 96 | + strm.write(header.getBytes()); |
| 97 | + header = null; |
| 98 | + } |
| 99 | + |
| 100 | + if (bufpos == 0) |
| 101 | + return; |
| 102 | + |
| 103 | + if (chunked) { |
| 104 | + String len = String.format("%04x", bufpos); |
| 105 | + strm.write(len.getBytes()); |
| 106 | + } |
| 107 | + strm.write(buffer, 0, bufpos); |
| 108 | + bufpos = 0; |
| 109 | + } |
| 110 | +} |
Index: trunk/imgserv/imgserv-server/src/imgservserver/MultiFormatImageFactory.java |
— | — | @@ -0,0 +1,116 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | + |
| 10 | +/* @(#) $Id$ */ |
| 11 | + |
| 12 | +package imgservserver; |
| 13 | + |
| 14 | +public class MultiFormatImageFactory { |
| 15 | + static class ImageHandler { |
| 16 | + Class handler; |
| 17 | + String name; |
| 18 | + |
| 19 | + public ImageHandler(String name, Class handler) { |
| 20 | + this.name = name; |
| 21 | + this.handler = handler; |
| 22 | + } |
| 23 | + |
| 24 | + public String getName() { |
| 25 | + return name; |
| 26 | + } |
| 27 | + |
| 28 | + public Class getHandler() { |
| 29 | + return handler; |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | + static String[][] normalisedNames = { |
| 34 | + { "png", "png" }, |
| 35 | + { "PNG", "png" }, |
| 36 | + { "jpeg", "jpeg" }, |
| 37 | + { "JPEG", "jpeg" }, |
| 38 | + { "JPG", "jpeg" }, |
| 39 | + { "jpg", "jpeg" }, |
| 40 | + { "bmp", "bmp" }, |
| 41 | + { "BMP", "bmp" }, |
| 42 | + { "tiff", "tiff" }, |
| 43 | + { "tif", "tiff" }, |
| 44 | + { "TIFF", "tiff" }, |
| 45 | + { "TIF", "tiff" }, |
| 46 | + { "svg", "svg" }, |
| 47 | + { "SVG", "svg" }, |
| 48 | + }; |
| 49 | + |
| 50 | + static String normaliseName(String name) { |
| 51 | + for(String[] s : normalisedNames) { |
| 52 | + if (s[0].equals(name)) |
| 53 | + return s[1]; |
| 54 | + } |
| 55 | + |
| 56 | + return null; |
| 57 | + } |
| 58 | + static ImageHandler[] readers = { |
| 59 | + new ImageHandler("png", ImageIOImageHandler.class), |
| 60 | + new ImageHandler("jpeg", ImageIOImageHandler.class), |
| 61 | + new ImageHandler("bmp", ImageIOImageHandler.class), |
| 62 | + new ImageHandler("tiff", TIFFImageHandler.class), |
| 63 | + new ImageHandler("svg", SVGImageHandler.class), |
| 64 | + }; |
| 65 | + |
| 66 | + static ImageHandler[] writers = { |
| 67 | + new ImageHandler("png", ImageIOImageHandler.class), |
| 68 | + new ImageHandler("jpeg", ImageIOImageHandler.class), |
| 69 | + new ImageHandler("bmp", ImageIOImageHandler.class), |
| 70 | + new ImageHandler("tiff", TIFFImageHandler.class), |
| 71 | + }; |
| 72 | + |
| 73 | + static ImageHandler findHandler(ImageHandler[] list, String name) { |
| 74 | + for (ImageHandler h: list) |
| 75 | + if (h.getName().equals(name)) |
| 76 | + return h; |
| 77 | + return null; |
| 78 | + } |
| 79 | + |
| 80 | + static Object getHandler(ImageHandler[] list, String format) |
| 81 | + throws ImageTranscoderException { |
| 82 | + format = normaliseName(format); |
| 83 | + |
| 84 | + ImageHandler h = findHandler(list, format); |
| 85 | + if (h == null) |
| 86 | + throw new ImageTranscoderException( |
| 87 | + "No handler found for type \"" + format + "\""); |
| 88 | + |
| 89 | + Class c = h.getHandler(); |
| 90 | + try { |
| 91 | + return c.getConstructor(String.class).newInstance(format); |
| 92 | + } catch (Exception e) { |
| 93 | + throw new ImageTranscoderException( |
| 94 | + "Cannot instantiate handler for format \""+format+"\"", e); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + public static MultiFormatImageReader getReader(String format) |
| 99 | + throws ImageTranscoderException { |
| 100 | + try { |
| 101 | + return (MultiFormatImageReader) getHandler(readers, format); |
| 102 | + } catch (Exception e) { |
| 103 | + throw new ImageTranscoderException( |
| 104 | + "Cannot instantiate handler for format \""+format+"\"", e); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + public static MultiFormatImageWriter getWriter(String format) |
| 109 | + throws ImageTranscoderException { |
| 110 | + try { |
| 111 | + return (MultiFormatImageWriter) getHandler(writers, format); |
| 112 | + } catch (Exception e) { |
| 113 | + throw new ImageTranscoderException( |
| 114 | + "Cannot instantiate handler for format \""+format+"\"", e); |
| 115 | + } |
| 116 | + } |
| 117 | +} |
Index: trunk/imgserv/imgserv-client/imgserv.cxx |
— | — | @@ -6,6 +6,7 @@ |
7 | 7 | #include <ios> |
8 | 8 | #include <sstream> |
9 | 9 | #include <vector> |
| 10 | +#include <iterator> |
10 | 11 | |
11 | 12 | #include <sys/types.h> |
12 | 13 | #include <sys/socket.h> |
— | — | @@ -25,6 +26,7 @@ |
26 | 27 | void usage(); |
27 | 28 | int safe_write(int s, void *data, size_t n); |
28 | 29 | std::string extract_extension(std::string const &); |
| 30 | + void dump_data(char *data, std::size_t n); |
29 | 31 | |
30 | 32 | } |
31 | 33 | |
— | — | @@ -197,63 +199,118 @@ |
198 | 200 | std::cerr << "done, " << wr << " bytes\n"; |
199 | 201 | std::cerr << "% Waiting for reply..."; |
200 | 202 | |
201 | | - /* |
202 | | - * The reply is either "OK <size>\r\n<data>", or "ERROR <message>\r\n"; |
203 | | - */ |
204 | | - int offs = 0; |
205 | | - std::fill(v.begin(), v.end(), 0); |
| 203 | + enum { |
| 204 | + READING_STATUS, |
| 205 | + READING_CHUNK_SIZE, |
| 206 | + READING_CHUNK |
| 207 | + } state = READING_STATUS; |
| 208 | + int chunksize; |
| 209 | + int bufpos = 0; |
| 210 | + int buflen = 0; |
| 211 | + ssize_t z; |
| 212 | + std::string s; |
| 213 | + std::vector<char>::iterator it, bufstart, bufend; |
| 214 | + static std::string const rn = "\r\n"; |
| 215 | + std::size_t outsize = 0; |
| 216 | + |
206 | 217 | for (;;) { |
207 | | - ssize_t r; |
208 | | - r = read(sock, &buf[0] + offs, buf.size() - offs - 1); |
209 | | - if (r == -1) { |
210 | | - std::cerr << "read error: " << |
211 | | - std::strerror(errno) << '\n'; |
212 | | - return 1; |
213 | | - } |
| 218 | + if (buflen == 0) { |
| 219 | + z = read(sock, &buf[0], buf.size()); |
| 220 | + if (z == -1) { |
| 221 | + std::cerr << "% Read error from server: " |
| 222 | + << std::strerror(errno) << '\n'; |
| 223 | + return 1; |
| 224 | + } else if (z == 0) { |
| 225 | + std::cerr << "% Unexpected EOF from server.\n"; |
| 226 | + return 1; |
| 227 | + } |
214 | 228 | |
215 | | - if (r == 0) { |
216 | | - std::cerr << "unexpected end of file\n"; |
217 | | - return 1; |
| 229 | + //dump_data(&buf[0], z); |
| 230 | + buflen = z; |
| 231 | + bufpos = 0; |
218 | 232 | } |
219 | 233 | |
220 | | - offs += r; |
| 234 | + bufstart = buf.begin() + bufpos; |
| 235 | + bufend = buf.begin() + buflen - bufpos; |
221 | 236 | |
222 | | - char *rn = std::strstr(&buf[0], "\r\n"); |
223 | | - if (rn != NULL) { |
224 | | - if (std::memcmp(&buf[0], "ERROR ", 6) == 0) { |
225 | | - std::cerr << "server error: " |
226 | | - << std::string(&buf[0] + 6, rn) |
227 | | - << '\n'; |
228 | | - return 1; |
229 | | - } else if (std::memcmp(&buf[0], "OK ", 3) == 0) { |
230 | | - std::cerr << "ok\n"; |
231 | | - outfile.write(rn + 2, |
232 | | - (&buf[0] + offs) - (rn + 2)); |
233 | | - break; |
| 237 | + switch (state) { |
| 238 | + case READING_STATUS: |
| 239 | + it = std::search(bufstart, bufend, |
| 240 | + rn.begin(), rn.end()); |
| 241 | + if (it != bufend) { |
| 242 | + int len = std::distance(bufstart, it); |
| 243 | + s.insert(s.end(), bufstart, it); |
| 244 | + bufpos += len + 2; |
| 245 | + buflen -= len + 2; |
| 246 | + if (s == "OK") { |
| 247 | + std::cout << "ok.\n"; |
| 248 | + } else { |
| 249 | + if (s.substr(0, 5) == "ERROR") { |
| 250 | + std::cout << "error: " << s.substr(6) << '\n'; |
| 251 | + } else { |
| 252 | + std::cout << "error: unknown status\n"; |
| 253 | + } |
| 254 | + return 1; |
| 255 | + } |
| 256 | + |
| 257 | + state = READING_CHUNK_SIZE; |
| 258 | + s = ""; |
| 259 | + continue; |
| 260 | + } else { |
| 261 | + if (s.size() + buflen > 8192) { |
| 262 | + std::cout << "error: header too long\n"; |
| 263 | + return 1; |
| 264 | + } |
| 265 | + |
| 266 | + s.insert(s.end(), bufstart, bufend); |
| 267 | + buflen = bufpos = 0; |
234 | 268 | } |
235 | | - } |
| 269 | + break; |
236 | 270 | |
237 | | - if (offs > 256) { |
238 | | - std::cerr << "too much garbage before reply.\n"; |
239 | | - return 1; |
240 | | - } |
241 | | - } |
| 271 | + case READING_CHUNK_SIZE: |
| 272 | + i = 4 - s.size(); |
| 273 | + while (i && buflen) { |
| 274 | + s += buf[bufpos]; |
| 275 | + bufpos++; |
| 276 | + buflen--; |
| 277 | + i--; |
| 278 | + } |
242 | 279 | |
243 | | - ssize_t outsize = 0; |
244 | | - for (;;) { |
245 | | - ssize_t n; |
246 | | - if ((n = read(sock, &buf[0], buf.size())) == -1) { |
247 | | - std::cerr << "read error: " << |
248 | | - std::strerror(errno) << '\n'; |
249 | | - return 1; |
250 | | - } |
| 280 | + if (i == 0) { |
| 281 | + chunksize = std::strtol(s.c_str(), NULL, 16); |
| 282 | + if (chunksize == 0) |
| 283 | + goto done; |
| 284 | + state = READING_CHUNK; |
| 285 | + s = ""; |
| 286 | + } |
| 287 | + continue; |
251 | 288 | |
252 | | - if (n == 0) |
253 | | - break; |
| 289 | + case READING_CHUNK: |
| 290 | + it = bufstart + std::min(chunksize, buflen); |
| 291 | + std::copy(bufstart, it, std::ostream_iterator<char>(outfile)); |
| 292 | + outsize += std::distance(bufstart, it); |
254 | 293 | |
255 | | - outsize += n; |
256 | | - outfile.write(&buf[0], n); |
| 294 | + if (buflen >= chunksize) { |
| 295 | + /* finished this chunk */ |
| 296 | + buflen -= chunksize; |
| 297 | + bufpos += chunksize; |
| 298 | + state = READING_CHUNK_SIZE; |
| 299 | + } else { |
| 300 | + chunksize -= buflen; |
| 301 | + bufpos = buflen = 0; |
| 302 | + |
| 303 | + if (chunksize == 0) { |
| 304 | + state = READING_CHUNK_SIZE; |
| 305 | + continue; |
| 306 | + } |
| 307 | + } |
| 308 | + continue; |
| 309 | + |
| 310 | + default: |
| 311 | + abort(); |
| 312 | + } |
257 | 313 | } |
| 314 | +done:; |
258 | 315 | |
259 | 316 | std::cerr << "% Wrote " << outsize << " bytes to " << argv[1] << '\n'; |
260 | 317 | } |
— | — | @@ -291,4 +348,13 @@ |
292 | 349 | return fname.substr(n + 1); |
293 | 350 | } |
294 | 351 | |
| 352 | +void |
| 353 | +dump_data(char *s, std::size_t n) |
| 354 | +{ |
| 355 | + while (n--) { |
| 356 | + std::printf("%02x", (int)(unsigned char)*s); |
| 357 | + s++; |
| 358 | + } |
295 | 359 | } |
| 360 | + |
| 361 | +} |