001/*
002 * Copyright 2006 - 2013
003 *     Stefan Balev     <stefan.balev@graphstream-project.org>
004 *     Julien Baudry    <julien.baudry@graphstream-project.org>
005 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
006 *     Yoann Pigné      <yoann.pigne@graphstream-project.org>
007 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
008 * 
009 * This file is part of GraphStream <http://graphstream-project.org>.
010 * 
011 * GraphStream is a library whose purpose is to handle static or dynamic
012 * graph, create them from scratch, file or any source and display them.
013 * 
014 * This program is free software distributed under the terms of two licenses, the
015 * CeCILL-C license that fits European law, and the GNU Lesser General Public
016 * License. You can  use, modify and/ or redistribute the software under the terms
017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
019 * the Free Software Foundation, either version 3 of the License, or (at your
020 * option) any later version.
021 * 
022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
024 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
025 * 
026 * You should have received a copy of the GNU Lesser General Public License
027 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
028 * 
029 * The fact that you are presently reading this means that you have had
030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
031 */
032package org.graphstream.stream.file;
033
034import java.awt.Graphics2D;
035import java.awt.image.BufferedImage;
036import java.io.File;
037import java.io.FileReader;
038import java.io.IOException;
039import java.io.OutputStream;
040import java.io.Writer;
041import java.util.Arrays;
042import java.util.HashMap;
043import java.util.LinkedList;
044import java.util.regex.Matcher;
045import java.util.regex.Pattern;
046
047import javax.imageio.ImageIO;
048
049import org.graphstream.graph.Graph;
050import org.graphstream.stream.GraphReplay;
051import org.graphstream.stream.ProxyPipe;
052import org.graphstream.stream.Sink;
053import org.graphstream.stream.file.FileSourceDGS;
054import org.graphstream.stream.thread.ThreadProxyPipe;
055import org.graphstream.ui.geom.Point3;
056import org.graphstream.ui.graphicGraph.GraphicGraph;
057import org.graphstream.ui.layout.Layout;
058import org.graphstream.ui.layout.LayoutRunner;
059import org.graphstream.ui.layout.Layouts;
060import org.graphstream.ui.swingViewer.GraphRenderer;
061
062/**
063 * Output graph in image files.
064 * 
065 * <p>
066 * Given a prefix "dir/prefix_" and an output policy, this sink will output
067 * graph in an image file which name is prefix + a growing counter.
068 * </p>
069 * <p>
070 * Then images can be processed to produce a movie. For example, with mencoder,
071 * the following produce high quality movie :
072 * </p>
073 * 
074 * <pre>
075 * 
076 * #!/bin/bash
077 * 
078 * EXT=png
079 * CODEC=msmpeg4v2
080 * BITRATE=6000
081 * OPT="vcodec=mpeg4:vqscale=2:vhq:v4mv:trell:autoaspect"
082 * FPS=15
083 * PREFIX=$1
084 * OUTPUT=$2
085 * 
086 * mencoder "mf://$PREFIX*.$EXT" -mf fps=$FPS:type=$EXT -ovc lavc -lavcopts $OPTS -o $OUTPUT -nosound -vf scale
087 * 
088 * </pre>
089 */
090public class FileSinkImages implements FileSink {
091        /**
092         * Output resolutions.
093         */
094        public static interface Resolution {
095                int getWidth();
096
097                int getHeight();
098        }
099
100        /**
101         * Common resolutions.
102         */
103        public static enum Resolutions implements Resolution {
104                QVGA(320, 240), CGA(320, 200), VGA(640, 480), NTSC(720, 480), PAL(768,
105                                576), WVGA_5by3(800, 480), SVGA(800, 600), WVGA_16by9(854, 480), WSVGA(
106                                1024, 600), XGA(1024, 768), HD720(1280, 720), WXGA_5by3(1280,
107                                768), WXGA_8by5(1280, 800), SXGA(1280, 1024), SXGAp(1400, 1050), WSXGAp(
108                                1680, 1050), UXGA(1600, 1200), HD1080(1920, 1080), WUXGA(1920,
109                                1200), TwoK(2048, 1080), QXGA(2048, 1536), WQXGA(2560, 1600), QSXGA(
110                                2560, 2048), UHD_4K(3840, 2160), UHD_8K_16by9(7680, 4320), UHD_8K_17by8(
111                                8192, 4320), UHD_8K_1by1(8192, 8192)
112
113                ;
114
115                final int width, height;
116
117                Resolutions(int width, int height) {
118                        this.width = width;
119                        this.height = height;
120                }
121
122                public int getWidth() {
123                        return width;
124                }
125
126                public int getHeight() {
127                        return height;
128                }
129
130                @Override
131                public String toString() {
132                        return String.format("%s (%dx%d)", name(), width, height);
133                }
134        }
135
136        /**
137         * User-defined resolution.
138         */
139        public static class CustomResolution implements Resolution {
140                final int width, height;
141
142                public CustomResolution(int width, int height) {
143                        this.width = width;
144                        this.height = height;
145                }
146
147                public int getWidth() {
148                        return width;
149                }
150
151                public int getHeight() {
152                        return height;
153                }
154
155                @Override
156                public String toString() {
157                        return String.format("%dx%d", width, height);
158                }
159        }
160
161        /**
162         * Output image type.
163         */
164        public static enum OutputType {
165                PNG(BufferedImage.TYPE_INT_ARGB, "png"), JPG(
166                                BufferedImage.TYPE_INT_RGB, "jpg"), png(
167                                BufferedImage.TYPE_INT_ARGB, "png"), jpg(
168                                BufferedImage.TYPE_INT_RGB, "jpg")
169
170                ;
171
172                final int imageType;
173                final String ext;
174
175                OutputType(int imageType, String ext) {
176                        this.imageType = imageType;
177                        this.ext = ext;
178                }
179        }
180
181        /**
182         * Output policy. Specify when an image is written. This is an important
183         * choice. Best choice is to divide the graph in steps and choose the
184         * *ByStepOutput*. Remember that if your graph has x nodes and
185         * *ByEventOutput* or *ByNodeEventOutput* is chosen, this will produce x
186         * images just for nodes creation.
187         */
188        public static enum OutputPolicy {
189                BY_EVENT, BY_ELEMENT_EVENT, BY_ATTRIBUTE_EVENT, BY_NODE_EVENT, BY_EDGE_EVENT, BY_GRAPH_EVENT, BY_STEP, BY_NODE_ADDED_REMOVED, BY_EDGE_ADDED_REMOVED, BY_NODE_ATTRIBUTE, BY_EDGE_ATTRIBUTE, BY_GRAPH_ATTRIBUTE, BY_LAYOUT_STEP, BY_NODE_MOVED, ON_RUNNER, NONE
190        }
191
192        /**
193         * Layout policy. Specify how layout is computed. It can be computed in its
194         * own thread with a LayoutRunner but if image rendering takes too much
195         * time, node positions will be very different between two images. To have a
196         * better result, we can choose to compute layout when a new image is
197         * rendered. This will smooth the move of nodes in the movie.
198         */
199        public static enum LayoutPolicy {
200                NO_LAYOUT, COMPUTED_IN_LAYOUT_RUNNER, COMPUTED_ONCE_AT_NEW_IMAGE, COMPUTED_FULLY_AT_NEW_IMAGE
201        }
202
203        /**
204         * Defines post rendering action on images.
205         */
206        public static interface PostRenderer {
207                void render(Graphics2D g);
208        }
209
210        /**
211         * Post rendering action allowing to add a logo-picture on images.
212         */
213        protected static class AddLogoRenderer implements PostRenderer {
214                /**
215                 * The logo.
216                 */
217                BufferedImage logo;
218                /**
219                 * Logo position on images.
220                 */
221                int x, y;
222
223                public AddLogoRenderer(String logoFile, int x, int y)
224                                throws IOException {
225                        File f = new File(logoFile);
226
227                        if (f.exists())
228                                this.logo = ImageIO.read(f);
229                        else
230                                this.logo = ImageIO.read(ClassLoader
231                                                .getSystemResource(logoFile));
232
233                        this.x = x;
234                        this.y = y;
235                }
236
237                public void render(Graphics2D g) {
238                        g.drawImage(logo, x, y, null);
239                }
240        }
241
242        /**
243         * Experimental. Allows to choose which renderer will be used.
244         */
245        public static enum RendererType {
246                BASIC(
247                                "org.graphstream.ui.swingViewer.basicRenderer.SwingBasicGraphRenderer"), SCALA(
248                                "org.graphstream.ui.j2dviewer.J2DGraphRenderer")
249
250                ;
251
252                final String classname;
253
254                RendererType(String cn) {
255                        this.classname = cn;
256                }
257        }
258
259        public static enum Quality {
260                LOW, MEDIUM, HIGH
261        }
262
263        protected Resolution resolution;
264        protected OutputType outputType;
265        protected GraphRenderer renderer;
266        protected String filePrefix;
267        protected BufferedImage image;
268        protected Graphics2D g2d;
269        protected final GraphicGraph gg;
270        protected Sink sink;
271        protected int counter;
272        protected OutputPolicy outputPolicy;
273        protected LinkedList<PostRenderer> postRenderers;
274        protected LayoutPolicy layoutPolicy;
275        protected LayoutRunner optLayout;
276        protected ProxyPipe layoutPipeIn;
277        protected Layout layout;
278        protected float layoutStabilizationLimit = 0.9f;
279        protected int layoutStepAfterStabilization = 10;
280        protected int layoutStepPerFrame = 4;
281        protected int layoutStepWithoutFrame = 0;
282        protected long outputRunnerDelay = 10;
283        protected boolean outputRunnerAlive = false;
284        protected OutputRunner outputRunner;
285        protected ThreadProxyPipe outputRunnerProxy;
286        protected boolean clearImageBeforeOutput = false;
287        protected boolean hasBegan = false;
288        protected boolean autofit = true;
289        protected String styleSheet = null;
290
291        public FileSinkImages() {
292                this(OutputType.PNG, Resolutions.HD720);
293        }
294
295        public FileSinkImages(OutputType type, Resolution resolution) {
296                this("frame_", type, resolution, OutputPolicy.NONE);
297        }
298
299        public FileSinkImages(String prefix, OutputType type,
300                        Resolution resolution, OutputPolicy outputPolicy) {
301                this.resolution = resolution;
302                this.outputType = type;
303                this.filePrefix = prefix;
304                this.counter = 0;
305                this.gg = new GraphicGraph(prefix);
306                this.postRenderers = new LinkedList<PostRenderer>();
307                this.layoutPolicy = LayoutPolicy.NO_LAYOUT;
308                this.layout = null;
309                this.optLayout = null;
310                this.layoutPipeIn = null;
311                this.sink = gg;
312
313                setOutputPolicy(outputPolicy);
314                setRenderer(RendererType.BASIC);
315
316                initImage();
317        }
318
319        /**
320         * Enable high-quality rendering and anti-aliasing.
321         */
322        public void setQuality(Quality q) {
323                switch (q) {
324                case LOW:
325                        if (gg.hasAttribute("ui.quality"))
326                                gg.removeAttribute("ui.quality");
327                        if (gg.hasAttribute("ui.antialias"))
328                                gg.removeAttribute("ui.antialias");
329
330                        break;
331                case MEDIUM:
332                        if (!gg.hasAttribute("ui.quality"))
333                                gg.addAttribute("ui.quality");
334                        if (gg.hasAttribute("ui.antialias"))
335                                gg.removeAttribute("ui.antialias");
336
337                        break;
338                case HIGH:
339                        if (!gg.hasAttribute("ui.quality"))
340                                gg.addAttribute("ui.quality");
341                        if (!gg.hasAttribute("ui.antialias"))
342                                gg.addAttribute("ui.antialias");
343
344                        break;
345                }
346        }
347
348        /**
349         * Defines style of the graph as a css stylesheet.
350         * 
351         * @param styleSheet
352         *            the style sheet
353         */
354        public void setStyleSheet(String styleSheet) {
355                this.styleSheet = styleSheet;
356                gg.addAttribute("ui.stylesheet", styleSheet);
357        }
358
359        /**
360         * Set resolution of images.
361         * 
362         * @param r
363         *            resolution
364         */
365        public void setResolution(Resolution r) {
366                if (r != resolution) {
367                        resolution = r;
368                        initImage();
369                }
370        }
371
372        /**
373         * Set a custom resolution.
374         * 
375         * @param width
376         * @param height
377         */
378        public void setResolution(int width, int height) {
379                if (resolution == null || resolution.getWidth() != width
380                                || resolution.getHeight() != height) {
381                        resolution = new CustomResolution(width, height);
382                        initImage();
383                }
384        }
385
386        /**
387         * Set the renderer type. This is experimental.
388         * 
389         * @param rendererType
390         */
391        @SuppressWarnings("unchecked")
392        public void setRenderer(RendererType rendererType) {
393                try {
394                        Class<? extends GraphRenderer> clazz = (Class<? extends GraphRenderer>) Class
395                                        .forName(rendererType.classname);
396
397                        GraphRenderer obj = clazz.newInstance();
398
399                        if (this.renderer != null)
400                                this.renderer.close();
401
402                        this.renderer = obj;
403                        this.renderer.open(gg, null);
404                } catch (ClassNotFoundException e) {
405                        e.printStackTrace();
406                } catch (ClassCastException e) {
407                        System.err
408                                        .printf("not a renderer \"%s\"%n", rendererType.classname);
409                } catch (InstantiationException e) {
410                        e.printStackTrace();
411                } catch (IllegalAccessException e) {
412                        e.printStackTrace();
413                }
414        }
415
416        /**
417         * Set the output policy.
418         * 
419         * @param policy
420         *            policy defining when images are produced
421         */
422        public void setOutputPolicy(OutputPolicy policy) {
423                this.outputPolicy = policy;
424        }
425
426        /**
427         * Set the layout policy.
428         * 
429         * @param policy
430         *            policy defining how the layout is computed
431         */
432        public synchronized void setLayoutPolicy(LayoutPolicy policy) {
433                if (policy != layoutPolicy) {
434                        switch (layoutPolicy) {
435                        case COMPUTED_IN_LAYOUT_RUNNER:
436                                // layout.removeListener(this);
437                                optLayout.release();
438                                optLayout = null;
439                                layoutPipeIn.removeAttributeSink(gg);
440                                layoutPipeIn = null;
441                                layout = null;
442                                break;
443                        case COMPUTED_ONCE_AT_NEW_IMAGE:
444                                // layout.removeListener(this);
445                                gg.removeSink(layout);
446                                layout.removeAttributeSink(gg);
447                                layout = null;
448                                break;
449                        default:
450                                break;
451                        }
452
453                        switch (policy) {
454                        case COMPUTED_IN_LAYOUT_RUNNER:
455                                layout = Layouts.newLayoutAlgorithm();
456                                optLayout = new InnerLayoutRunner();
457                                break;
458                        case COMPUTED_FULLY_AT_NEW_IMAGE:
459                        case COMPUTED_ONCE_AT_NEW_IMAGE:
460                                layout = Layouts.newLayoutAlgorithm();
461                                gg.addSink(layout);
462                                layout.addAttributeSink(gg);
463                                break;
464                        default:
465                                break;
466                        }
467
468                        // layout.addListener(this);
469                        layoutPolicy = policy;
470                }
471        }
472
473        /**
474         * Set the amount of step before output a new image. This is used only in
475         * ByLayoutStepOutput output policy.
476         * 
477         * @param spf
478         *            step per frame
479         */
480        public void setLayoutStepPerFrame(int spf) {
481                this.layoutStepPerFrame = spf;
482        }
483
484        /**
485         * Set the amount of steps after the stabilization of the algorithm.
486         * 
487         * @param sas
488         *            step after stabilization.
489         */
490        public void setLayoutStepAfterStabilization(int sas) {
491                this.layoutStepAfterStabilization = sas;
492        }
493
494        /**
495         * Set the stabilization limit of the layout used to compute coordinates of
496         * nodes. See
497         * {@link org.graphstream.ui.layout.Layout#setStabilizationLimit(double)}
498         * for more informations about this limit.
499         * 
500         * @param limit
501         */
502        public void setLayoutStabilizationLimit(double limit) {
503                if (layout == null)
504                        throw new NullPointerException("did you enable layout ?");
505
506                layout.setStabilizationLimit(limit);
507        }
508
509        /**
510         * Add a logo on images.
511         * 
512         * @param logoFile
513         *            path to the logo picture-file
514         * @param x
515         *            x position of the logo (top-left corner is (0;0))
516         * @param y
517         *            y position of the logo
518         */
519        public void addLogo(String logoFile, int x, int y) {
520                PostRenderer pr;
521
522                try {
523                        pr = new AddLogoRenderer(logoFile, x, y);
524                        postRenderers.add(pr);
525                } catch (IOException e) {
526                        e.printStackTrace();
527                }
528        }
529
530        public synchronized void setOutputRunnerEnabled(boolean on) {
531                if (!on && outputRunnerAlive) {
532                        outputRunnerAlive = false;
533
534                        try {
535                                if (outputRunner != null)
536                                        outputRunner.join();
537                        } catch (InterruptedException e) {
538                                // ... ?
539                        }
540
541                        outputRunner = null;
542                        sink = gg;
543
544                        if (outputRunnerProxy != null)
545                                outputRunnerProxy.pump();
546                }
547
548                outputRunnerAlive = on;
549
550                if (outputRunnerAlive) {
551                        if (outputRunnerProxy == null) {
552                                outputRunnerProxy = new ThreadProxyPipe();
553                                outputRunnerProxy.init(gg);
554                        }
555
556                        sink = outputRunnerProxy;
557                        outputRunner = new OutputRunner();
558                        outputRunner.start();
559                }
560        }
561
562        public void setOutputRunnerDelay(long delay) {
563                outputRunnerDelay = delay;
564        }
565
566        public void stabilizeLayout(double limit) {
567                if (layout != null) {
568                        while (layout.getStabilization() < limit)
569                                layout.compute();
570                }
571        }
572
573        public Point3 getViewCenter() {
574                return renderer.getCamera().getViewCenter();
575        }
576
577        public void setViewCenter(double x, double y) {
578                renderer.getCamera().setViewCenter(x, y, 0);
579        }
580
581        public double getViewPercent() {
582                return renderer.getCamera().getViewPercent();
583        }
584
585        public void setViewPercent(double zoom) {
586                renderer.getCamera().setViewPercent(zoom);
587        }
588
589        public void setGraphViewport(double minx, double miny, double maxx,
590                        double maxy) {
591                renderer.getCamera().setGraphViewport(minx, miny, maxx, maxy);
592        }
593
594        public void setClearImageBeforeOutputEnabled(boolean on) {
595                clearImageBeforeOutput = on;
596        }
597
598        public void setAutofit(boolean on) {
599                autofit = on;
600        }
601
602        protected void initImage() {
603                image = new BufferedImage(resolution.getWidth(),
604                                resolution.getHeight(), outputType.imageType);
605
606                g2d = image.createGraphics();
607        }
608
609        protected void clearGG() {
610                gg.clear();
611
612                if (styleSheet != null)
613                        gg.setAttribute("ui.stylesheet", styleSheet);
614
615                if (layout != null)
616                        layout.clear();
617        }
618
619        /**
620         * Produce a new image.
621         */
622        public void outputNewImage() {
623                outputNewImage(String.format("%s%06d.%s", filePrefix, counter++,
624                                outputType.ext));
625        }
626
627        public synchronized void outputNewImage(String filename) {
628                switch (layoutPolicy) {
629                case COMPUTED_IN_LAYOUT_RUNNER:
630                        layoutPipeIn.pump();
631                        break;
632                case COMPUTED_ONCE_AT_NEW_IMAGE:
633                        if (layout != null)
634                                layout.compute();
635                        break;
636                case COMPUTED_FULLY_AT_NEW_IMAGE:
637                        stabilizeLayout(layout.getStabilizationLimit());
638                        break;
639                default:
640                        break;
641                }
642
643                if (resolution.getWidth() != image.getWidth()
644                                || resolution.getHeight() != image.getHeight())
645                        initImage();
646
647                if (clearImageBeforeOutput) {
648                        for (int x = 0; x < resolution.getWidth(); x++)
649                                for (int y = 0; y < resolution.getHeight(); y++)
650                                        image.setRGB(x, y, 0x00000000);
651                }
652
653                if (gg.getNodeCount() > 0) {
654                        if (autofit) {
655                                gg.computeBounds();
656
657                                Point3 lo = gg.getMinPos();
658                                Point3 hi = gg.getMaxPos();
659
660                                renderer.getCamera().setBounds(lo.x, lo.y, lo.z, hi.x, hi.y,
661                                                hi.z);
662                        }
663
664                        renderer.render(g2d, 0, 0, resolution.getWidth(),
665                                        resolution.getHeight());
666                }
667
668                for (PostRenderer action : postRenderers)
669                        action.render(g2d);
670
671                image.flush();
672
673                try {
674                        File out = new File(filename);
675
676                        if (out.getParent() != null && !out.getParentFile().exists())
677                                out.getParentFile().mkdirs();
678
679                        ImageIO.write(image, outputType.name(), out);
680
681                        printProgress();
682                } catch (IOException e) {
683                        // ?
684                }
685        }
686
687        protected void printProgress() {
688                System.out.printf("\033[s\033[K%d images written\033[u", counter);
689        }
690
691        /*
692         * (non-Javadoc)
693         * 
694         * @see org.graphstream.stream.file.FileSink#begin(java.io.OutputStream)
695         */
696        public void begin(OutputStream stream) throws IOException {
697                throw new IOException("not implemented");
698        }
699
700        /*
701         * (non-Javadoc)
702         * 
703         * @see org.graphstream.stream.file.FileSink#begin(java.io.Writer)
704         */
705        public void begin(Writer writer) throws IOException {
706                throw new IOException("not implemented");
707        }
708
709        /*
710         * (non-Javadoc)
711         * 
712         * @see org.graphstream.stream.file.FileSink#begin(java.lang.String)
713         */
714        public void begin(String prefix) throws IOException {
715                this.filePrefix = prefix;
716                this.hasBegan = true;
717        }
718
719        /*
720         * (non-Javadoc)
721         * 
722         * @see org.graphstream.stream.file.FileSink#flush()
723         */
724        public void flush() throws IOException {
725                // Nothing to do
726        }
727
728        /*
729         * (non-Javadoc)
730         * 
731         * @see org.graphstream.stream.file.FileSink#end()
732         */
733        public void end() throws IOException {
734                flush();
735                this.hasBegan = false;
736        }
737
738        /*
739         * (non-Javadoc)
740         * 
741         * @see
742         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
743         * , java.io.OutputStream)
744         */
745        public void writeAll(Graph g, OutputStream stream) throws IOException {
746                throw new IOException("not implemented");
747        }
748
749        /*
750         * (non-Javadoc)
751         * 
752         * @see
753         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
754         * , java.io.Writer)
755         */
756        public void writeAll(Graph g, Writer writer) throws IOException {
757                throw new IOException("not implemented");
758        }
759
760        /*
761         * (non-Javadoc)
762         * 
763         * @see
764         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
765         * , java.lang.String)
766         */
767        public synchronized void writeAll(Graph g, String filename)
768                        throws IOException {
769                clearGG();
770
771                GraphReplay replay = new GraphReplay(String.format(
772                                "file_sink_image-write_all-replay-%x", System.nanoTime()));
773
774                replay.addSink(gg);
775                replay.replay(g);
776                replay.removeSink(gg);
777
778                outputNewImage(filename);
779
780                clearGG();
781        }
782
783        /**
784         * @see org.graphstream.stream.Sink
785         */
786        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
787                        String attribute, Object value) {
788                sink.edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value);
789
790                switch (outputPolicy) {
791                case BY_EVENT:
792                case BY_EDGE_EVENT:
793                case BY_EDGE_ATTRIBUTE:
794                case BY_ATTRIBUTE_EVENT:
795                        if (hasBegan)
796                                outputNewImage();
797                        break;
798                default:
799                        break;
800                }
801        }
802
803        /**
804         * @see org.graphstream.stream.Sink
805         */
806        public void edgeAttributeChanged(String sourceId, long timeId,
807                        String edgeId, String attribute, Object oldValue, Object newValue) {
808                sink.edgeAttributeChanged(sourceId, timeId, edgeId, attribute,
809                                oldValue, newValue);
810
811                switch (outputPolicy) {
812                case BY_EVENT:
813                case BY_EDGE_EVENT:
814                case BY_EDGE_ATTRIBUTE:
815                case BY_ATTRIBUTE_EVENT:
816                        if (hasBegan)
817                                outputNewImage();
818                        break;
819                default:
820                        break;
821                }
822        }
823
824        /**
825         * @see org.graphstream.stream.Sink
826         */
827        public void edgeAttributeRemoved(String sourceId, long timeId,
828                        String edgeId, String attribute) {
829                sink.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute);
830
831                switch (outputPolicy) {
832                case BY_EVENT:
833                case BY_EDGE_EVENT:
834                case BY_EDGE_ATTRIBUTE:
835                case BY_ATTRIBUTE_EVENT:
836                        if (hasBegan)
837                                outputNewImage();
838                        break;
839                default:
840                        break;
841                }
842        }
843
844        /**
845         * @see org.graphstream.stream.Sink
846         */
847        public void graphAttributeAdded(String sourceId, long timeId,
848                        String attribute, Object value) {
849                sink.graphAttributeAdded(sourceId, timeId, attribute, value);
850
851                switch (outputPolicy) {
852                case BY_EVENT:
853                case BY_GRAPH_EVENT:
854                case BY_GRAPH_ATTRIBUTE:
855                case BY_ATTRIBUTE_EVENT:
856                        if (hasBegan)
857                                outputNewImage();
858                        break;
859                default:
860                        break;
861                }
862        }
863
864        /**
865         * @see org.graphstream.stream.Sink
866         */
867        public void graphAttributeChanged(String sourceId, long timeId,
868                        String attribute, Object oldValue, Object newValue) {
869                sink.graphAttributeChanged(sourceId, timeId, attribute, oldValue,
870                                newValue);
871
872                switch (outputPolicy) {
873                case BY_EVENT:
874                case BY_GRAPH_EVENT:
875                case BY_GRAPH_ATTRIBUTE:
876                case BY_ATTRIBUTE_EVENT:
877                        if (hasBegan)
878                                outputNewImage();
879                        break;
880                default:
881                        break;
882                }
883        }
884
885        /**
886         * @see org.graphstream.stream.Sink
887         */
888        public void graphAttributeRemoved(String sourceId, long timeId,
889                        String attribute) {
890                sink.graphAttributeRemoved(sourceId, timeId, attribute);
891
892                switch (outputPolicy) {
893                case BY_EVENT:
894                case BY_GRAPH_EVENT:
895                case BY_GRAPH_ATTRIBUTE:
896                case BY_ATTRIBUTE_EVENT:
897                        if (hasBegan)
898                                outputNewImage();
899                        break;
900                default:
901                        break;
902                }
903        }
904
905        /**
906         * @see org.graphstream.stream.Sink
907         */
908        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
909                        String attribute, Object value) {
910                sink.nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value);
911
912                switch (outputPolicy) {
913                case BY_EVENT:
914                case BY_NODE_EVENT:
915                case BY_NODE_ATTRIBUTE:
916                case BY_ATTRIBUTE_EVENT:
917                        if (hasBegan)
918                                outputNewImage();
919                        break;
920                default:
921                        break;
922                }
923        }
924
925        /**
926         * @see org.graphstream.stream.Sink
927         */
928        public void nodeAttributeChanged(String sourceId, long timeId,
929                        String nodeId, String attribute, Object oldValue, Object newValue) {
930                sink.nodeAttributeChanged(sourceId, timeId, nodeId, attribute,
931                                oldValue, newValue);
932
933                switch (outputPolicy) {
934                case BY_EVENT:
935                case BY_NODE_EVENT:
936                case BY_NODE_ATTRIBUTE:
937                case BY_ATTRIBUTE_EVENT:
938                        if (hasBegan)
939                                outputNewImage();
940                        break;
941                default:
942                        break;
943                }
944        }
945
946        /**
947         * @see org.graphstream.stream.Sink
948         */
949        public void nodeAttributeRemoved(String sourceId, long timeId,
950                        String nodeId, String attribute) {
951                sink.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute);
952
953                switch (outputPolicy) {
954                case BY_EVENT:
955                case BY_NODE_EVENT:
956                case BY_NODE_ATTRIBUTE:
957                case BY_ATTRIBUTE_EVENT:
958                        if (hasBegan)
959                                outputNewImage();
960                        break;
961                default:
962                        break;
963                }
964        }
965
966        /**
967         * @see org.graphstream.stream.Sink
968         */
969        public void edgeAdded(String sourceId, long timeId, String edgeId,
970                        String fromNodeId, String toNodeId, boolean directed) {
971                sink.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId, directed);
972
973                switch (outputPolicy) {
974                case BY_EVENT:
975                case BY_EDGE_EVENT:
976                case BY_EDGE_ADDED_REMOVED:
977                case BY_ELEMENT_EVENT:
978                        if (hasBegan)
979                                outputNewImage();
980                        break;
981                default:
982                        break;
983                }
984        }
985
986        /**
987         * @see org.graphstream.stream.Sink
988         */
989        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
990                sink.edgeRemoved(sourceId, timeId, edgeId);
991
992                switch (outputPolicy) {
993                case BY_EVENT:
994                case BY_EDGE_EVENT:
995                case BY_EDGE_ADDED_REMOVED:
996                case BY_ELEMENT_EVENT:
997                        if (hasBegan)
998                                outputNewImage();
999                        break;
1000                default:
1001                        break;
1002                }
1003        }
1004
1005        /**
1006         * @see org.graphstream.stream.Sink
1007         */
1008        public void graphCleared(String sourceId, long timeId) {
1009                sink.graphCleared(sourceId, timeId);
1010
1011                switch (outputPolicy) {
1012                case BY_EVENT:
1013                case BY_GRAPH_EVENT:
1014                case BY_NODE_ADDED_REMOVED:
1015                case BY_EDGE_ADDED_REMOVED:
1016                case BY_ELEMENT_EVENT:
1017                        if (hasBegan)
1018                                outputNewImage();
1019                        break;
1020                default:
1021                        break;
1022                }
1023        }
1024
1025        /**
1026         * @see org.graphstream.stream.Sink
1027         */
1028        public void nodeAdded(String sourceId, long timeId, String nodeId) {
1029                sink.nodeAdded(sourceId, timeId, nodeId);
1030
1031                switch (outputPolicy) {
1032                case BY_EVENT:
1033                case BY_NODE_EVENT:
1034                case BY_NODE_ADDED_REMOVED:
1035                case BY_ELEMENT_EVENT:
1036                        if (hasBegan)
1037                                outputNewImage();
1038                        break;
1039                default:
1040                        break;
1041                }
1042        }
1043
1044        /**
1045         * @see org.graphstream.stream.Sink
1046         */
1047        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
1048                sink.nodeRemoved(sourceId, timeId, nodeId);
1049
1050                switch (outputPolicy) {
1051                case BY_EVENT:
1052                case BY_NODE_EVENT:
1053                case BY_NODE_ADDED_REMOVED:
1054                case BY_ELEMENT_EVENT:
1055                        if (hasBegan)
1056                                outputNewImage();
1057                        break;
1058                default:
1059                        break;
1060                }
1061        }
1062
1063        /**
1064         * @see org.graphstream.stream.Sink
1065         */
1066        public void stepBegins(String sourceId, long timeId, double step) {
1067                sink.stepBegins(sourceId, timeId, step);
1068
1069                switch (outputPolicy) {
1070                case BY_EVENT:
1071                case BY_STEP:
1072                        if (hasBegan)
1073                                outputNewImage();
1074                        break;
1075                default:
1076                        break;
1077                }
1078        }
1079
1080        // public void nodeMoved(String id, double x, double y, double z) {
1081        // switch (outputPolicy) {
1082        // case BY_NODE_MOVED:
1083        // if (hasBegan)
1084        // outputNewImage();
1085        // break;
1086        // }
1087        // }
1088
1089        // public void nodeInfos(String id, double dx, double dy, double dz) {
1090        // }
1091
1092        // public void edgeChanged(String id, double[] points) {
1093        // }
1094
1095        // public void nodesMoved(Map<String, double[]> nodes) {
1096        // switch (outputPolicy) {
1097        // case BY_NODE_MOVED:
1098        // if (hasBegan)
1099        // outputNewImage();
1100        // break;
1101        // }
1102        // }
1103
1104        // public void edgesChanged(Map<String, double[]> edges) {
1105        // }
1106
1107        // public void stepCompletion(double percent) {
1108        // switch (outputPolicy) {
1109        // case BY_LAYOUT_STEP:
1110        // layoutStepWithoutFrame++;
1111        //
1112        // if (layoutStepWithoutFrame >= layoutStepPerFrame) {
1113        // if (hasBegan)
1114        // outputNewImage();
1115        // layoutStepWithoutFrame = 0;
1116        // }
1117        //
1118        // break;
1119        // }
1120        // }
1121
1122        public static enum Option {
1123                IMAGE_PREFIX("image-prefix", 'p', "prefix of outputted images", true,
1124                                true, "image_"), IMAGE_TYPE("image-type", 't',
1125                                "image type. one of " + Arrays.toString(OutputType.values()),
1126                                true, true, "PNG"), IMAGE_RESOLUTION("image-resolution", 'r',
1127                                "defines images resolution. \"width x height\" or one of "
1128                                                + Arrays.toString(Resolutions.values()), true, true,
1129                                "HD720"), OUTPUT_POLICY("output-policy", 'e',
1130                                "defines when images are outputted. one of "
1131                                                + Arrays.toString(OutputPolicy.values()), true, true,
1132                                "ByStepOutput"), LOGO("logo", 'l', "add a logo to images",
1133                                true, true, null), STYLESHEET("stylesheet", 's',
1134                                "defines stylesheet of graph. can be a file or a string.",
1135                                true, true, null), QUALITY("quality", 'q',
1136                                "defines quality of rendering. one of "
1137                                                + Arrays.toString(Quality.values()), true, true, "HIGH");
1138                String fullopts;
1139                char shortopts;
1140                String description;
1141                boolean optional;
1142                boolean valuable;
1143                String defaultValue;
1144
1145                Option(String fullopts, char shortopts, String description,
1146                                boolean optional, boolean valuable, String defaultValue) {
1147                        this.fullopts = fullopts;
1148                        this.shortopts = shortopts;
1149                        this.description = description;
1150                        this.optional = optional;
1151                        this.valuable = valuable;
1152                        this.defaultValue = defaultValue;
1153                }
1154        }
1155
1156        protected class InnerLayoutRunner extends LayoutRunner {
1157
1158                public InnerLayoutRunner() {
1159                        super(FileSinkImages.this.gg, FileSinkImages.this.layout, true,
1160                                        true);
1161
1162                        FileSinkImages.this.layoutPipeIn = newLayoutPipe();
1163                        FileSinkImages.this.layoutPipeIn
1164                                        .addAttributeSink(FileSinkImages.this.gg);
1165                }
1166
1167                public void run() {
1168
1169                        int stepAfterStabilization = 0;
1170
1171                        do {
1172                                pumpPipe.pump();
1173                                layout.compute();
1174
1175                                if (layout.getStabilization() > layout.getStabilizationLimit())
1176                                        stepAfterStabilization++;
1177                                else
1178                                        stepAfterStabilization = 0;
1179
1180                                nap(80);
1181
1182                                if (stepAfterStabilization > layoutStepAfterStabilization)
1183                                        loop = false;
1184                        } while (loop);
1185                }
1186        }
1187
1188        protected class OutputRunner extends Thread {
1189                public OutputRunner() {
1190                        setDaemon(true);
1191                }
1192
1193                public void run() {
1194                        while (outputRunnerAlive && outputPolicy == OutputPolicy.ON_RUNNER) {
1195                                outputRunnerProxy.pump();
1196                                if (hasBegan)
1197                                        outputNewImage();
1198
1199                                try {
1200                                        Thread.sleep(outputRunnerDelay);
1201                                } catch (InterruptedException e) {
1202                                        outputRunnerAlive = false;
1203                                }
1204                        }
1205                }
1206        }
1207
1208        public static void usage() {
1209                System.out.printf("usage: java %s [options] fichier.dgs%n",
1210                                FileSinkImages.class.getName());
1211                System.out.printf("where options in:%n");
1212                for (Option option : Option.values()) {
1213                        System.out.printf("%n --%s%s , -%s %s%n%s%n", option.fullopts,
1214                                        option.valuable ? "=..." : "", option.shortopts,
1215                                        option.valuable ? "..." : "", option.description);
1216                }
1217        }
1218
1219        public static void main(String... args) throws IOException {
1220
1221                HashMap<Option, String> options = new HashMap<Option, String>();
1222                LinkedList<String> others = new LinkedList<String>();
1223
1224                for (Option option : Option.values())
1225                        if (option.defaultValue != null)
1226                                options.put(option, option.defaultValue);
1227
1228                if (args != null && args.length > 0) {
1229                        Pattern valueGetter = Pattern
1230                                        .compile("^--\\w[\\w-]*\\w?(?:=(?:\"([^\"]*)\"|([^\\s]*)))$");
1231
1232                        for (int i = 0; i < args.length; i++) {
1233
1234                                if (args[i]
1235                                                .matches("^--\\w[\\w-]*\\w?(=(\"[^\"]*\"|[^\\s]*))?$")) {
1236                                        boolean found = false;
1237                                        for (Option option : Option.values()) {
1238                                                if (args[i].startsWith("--" + option.fullopts + "=")) {
1239                                                        Matcher m = valueGetter.matcher(args[i]);
1240
1241                                                        if (m.matches()) {
1242                                                                options.put(
1243                                                                                option,
1244                                                                                m.group(1) == null ? m.group(2) : m
1245                                                                                                .group(1));
1246                                                        }
1247
1248                                                        found = true;
1249                                                        break;
1250                                                }
1251                                        }
1252
1253                                        if (!found) {
1254                                                System.err.printf("unknown option: %s%n",
1255                                                                args[i].substring(0, args[i].indexOf('=')));
1256                                                System.exit(1);
1257                                        }
1258                                } else if (args[i].matches("^-\\w$")) {
1259                                        boolean found = false;
1260
1261                                        for (Option option : Option.values()) {
1262                                                if (args[i].equals("-" + option.shortopts)) {
1263                                                        options.put(option, args[++i]);
1264                                                        break;
1265                                                }
1266                                        }
1267
1268                                        if (!found) {
1269                                                System.err.printf("unknown option: %s%n", args[i]);
1270                                                System.exit(1);
1271                                        }
1272                                } else {
1273                                        others.addLast(args[i]);
1274                                }
1275                        }
1276                } else {
1277                        usage();
1278                        System.exit(0);
1279                }
1280
1281                LinkedList<String> errors = new LinkedList<String>();
1282
1283                if (others.size() == 0) {
1284                        errors.add("dgs file name missing.");
1285                }
1286
1287                String imagePrefix;
1288                OutputType outputType = null;
1289                OutputPolicy outputPolicy = null;
1290                Resolution resolution = null;
1291                Quality quality = null;
1292                String logo;
1293                String stylesheet;
1294
1295                imagePrefix = options.get(Option.IMAGE_PREFIX);
1296
1297                try {
1298                        outputType = OutputType.valueOf(options.get(Option.IMAGE_TYPE));
1299                } catch (IllegalArgumentException e) {
1300                        errors.add("bad image type: " + options.get(Option.IMAGE_TYPE));
1301                }
1302
1303                try {
1304                        outputPolicy = OutputPolicy.valueOf(options
1305                                        .get(Option.OUTPUT_POLICY));
1306                } catch (IllegalArgumentException e) {
1307                        errors.add("bad output policy: "
1308                                        + options.get(Option.OUTPUT_POLICY));
1309                }
1310
1311                try {
1312                        quality = Quality.valueOf(options.get(Option.QUALITY));
1313                } catch (IllegalArgumentException e) {
1314                        errors.add("bad quality: " + options.get(Option.QUALITY));
1315                }
1316
1317                logo = options.get(Option.LOGO);
1318                stylesheet = options.get(Option.STYLESHEET);
1319
1320                try {
1321                        resolution = Resolutions.valueOf(options
1322                                        .get(Option.IMAGE_RESOLUTION));
1323                } catch (IllegalArgumentException e) {
1324                        Pattern p = Pattern.compile("^\\s*(\\d+)\\s*x\\s*(\\d+)\\s*$");
1325                        Matcher m = p.matcher(options.get(Option.IMAGE_RESOLUTION));
1326
1327                        if (m.matches()) {
1328                                resolution = new CustomResolution(Integer.parseInt(m.group(1)),
1329                                                Integer.parseInt(m.group(2)));
1330                        } else {
1331                                errors.add("bad resolution: "
1332                                                + options.get(Option.IMAGE_RESOLUTION));
1333                        }
1334                }
1335
1336                if (stylesheet != null && stylesheet.length() < 1024) {
1337                        File test = new File(stylesheet);
1338
1339                        if (test.exists()) {
1340                                FileReader in = new FileReader(test);
1341                                char[] buffer = new char[128];
1342                                String content = "";
1343
1344                                while (in.ready()) {
1345                                        int c = in.read(buffer, 0, 128);
1346                                        content += new String(buffer, 0, c);
1347                                }
1348
1349                                stylesheet = content;
1350                                in.close();
1351                        }
1352                }
1353
1354                {
1355                        File test = new File(others.peek());
1356                        if (!test.exists())
1357                                errors.add(String.format("file \"%s\" does not exist",
1358                                                others.peek()));
1359                }
1360
1361                if (errors.size() > 0) {
1362                        System.err.printf("error:%n");
1363
1364                        for (String error : errors)
1365                                System.err.printf("- %s%n", error);
1366
1367                        System.exit(1);
1368                }
1369
1370                FileSourceDGS dgs = new FileSourceDGS();
1371                FileSinkImages fsi = new FileSinkImages(imagePrefix, outputType,
1372                                resolution, outputPolicy);
1373
1374                dgs.addSink(fsi);
1375
1376                if (logo != null)
1377                        fsi.addLogo(logo, 0, 0);
1378
1379                fsi.setQuality(quality);
1380                if (stylesheet != null)
1381                        fsi.setStyleSheet(stylesheet);
1382
1383                boolean next = true;
1384
1385                dgs.begin(others.get(0));
1386
1387                while (next)
1388                        next = dgs.nextStep();
1389
1390                dgs.end();
1391        }
1392}