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}