1 package com.hammurapi.jcapture;
2 
3 import java.awt.AlphaComposite;
4 import java.awt.Dimension;
5 import java.awt.Graphics2D;
6 import java.awt.Point;
7 import java.awt.RenderingHints;
8 import java.awt.image.BufferedImage;
9 import java.io.ByteArrayInputStream;
10 import java.io.ByteArrayOutputStream;
11 import java.io.File;
12 import java.io.OutputStream;
13 import java.text.MessageFormat;
14 import java.util.ArrayList;
15 import java.util.IdentityHashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.atomic.AtomicInteger;
19 
20 import javax.imageio.ImageIO;
21 import javax.swing.JOptionPane;
22 import javax.swing.ProgressMonitor;
23 
24 import com.flagstone.transform.Background;
25 import com.flagstone.transform.DefineTag;
26 import com.flagstone.transform.DoAction;
27 import com.flagstone.transform.Movie;
28 import com.flagstone.transform.MovieHeader;
29 import com.flagstone.transform.MovieTag;
30 import com.flagstone.transform.Place2;
31 import com.flagstone.transform.Remove;
32 import com.flagstone.transform.Remove2;
33 import com.flagstone.transform.ShowFrame;
34 import com.flagstone.transform.action.Action;
35 import com.flagstone.transform.action.BasicAction;
36 import com.flagstone.transform.coder.Coder;
37 import com.flagstone.transform.datatype.Bounds;
38 import com.flagstone.transform.datatype.CoordTransform;
39 import com.flagstone.transform.datatype.WebPalette;
40 import com.flagstone.transform.image.ImageTag;
41 import com.flagstone.transform.util.image.ImageDecoder;
42 import com.flagstone.transform.util.image.ImageRegistry;
43 import com.flagstone.transform.util.image.ImageShape;
44 import com.flagstone.transform.util.shape.Canvas;
45 import com.flagstone.transform.util.sound.SoundFactory;
46 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame;
47 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape;
48 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.Image;
49 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ImageReference;
50 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ShapeContent;
51 
52 public class SwfEncoder implements VideoEncoder {
53 
54 	@Override
getFileExtension()55 	public String getFileExtension() {
56 		return "swf";
57 	}
58 
59 	@Override
getMimeType()60 	public String getMimeType() {
61 		return "application/x-shockwave-flash";
62 	}
63 
64 	@Override
toString()65 	public String toString() {
66 		return "SWF";
67 	}
68 
69 	@Override
encode(Config config, com.hammurapi.jcapture.Movie source, OutputStream out)70 	public Dimension encode(Config config,
71 		com.hammurapi.jcapture.Movie source,
72 		OutputStream out)
73 		throws Exception {
74 
75         AtomicInteger uid = new AtomicInteger();
76 
77         /**
78          * For reusing shape id's.
79          */
80         int maxId = Coder.USHORT_MAX;
81 
82         ButtonManager manager = null;
83         if (config.isToolBar()) {
84 	    	manager = new ButtonManager();
85 	    	manager.loadLibrary(getClass().getResource("toolbar_buttons.swf"));
86         	uid.set(manager.maxIdentifier()+1);
87         }
88 
89         Canvas path = new Canvas();
90         path.setPixels(false);
91 
92         int minImgLayer = 10;
93         int imgLayer = minImgLayer;
94         int maxImgLayer = maxId - 1000;
95 
96         int mouseLayer = maxImgLayer+1;
97         int mouseUid = -1;
98     	Place2 mousePlace = null;
99     	ImageTag mouseImage = null;
100 
101         int layer = maxImgLayer+2;
102 
103         int totalWork = 0;
104         for (Fragment frg: source.getFragments()) {
105         	totalWork = frg.getFrames().size()+1;
106         }
107 
108         ProgressMonitor progressMonitor = new ProgressMonitor(config.getParentComponent(), "Encoding to SWF", "Composing movie", 0, totalWork);
109         int progressCounter = 0;
110 
111         progressMonitor.setNote("Composing movie");
112         boolean firstFrame = true;
113         Dimension ret = null;
114         Map<Image, ImageTag> imageCache = new IdentityHashMap<Image, ImageTag>();
115 
116         Movie movie = new Movie();
117 
118         Point prevMouseLocation = null;
119 
120         int frameNo = 0;
121 
122         for (Fragment fragment: source.getFragments()) {
123 
124 	        SoundFactory soundFactory = null;
125 	        boolean soundHeaderAdded = false;
126 	        File audio = fragment.getAudio();
127 	        if (audio!=null) {
128 		        progressMonitor.setNote("Loading sound");
129 		        soundFactory = new SoundFactory();
130 
131 		 		// MP3 conversion
132 		 		if (config.getMp3command()!=null && config.getMp3command().trim().length()>0) {
133 			 		audio = new File(audio.getAbsolutePath()+".mp3");
134 			 		Runtime runtime = Runtime.getRuntime();
135 				    Process proc = runtime.exec(MessageFormat.format(config.getMp3command(), new Object[] {fragment.getAudio().getAbsolutePath(), audio.getAbsolutePath()}));
136 				    proc.waitFor();
137 				    if (!fragment.getAudio().delete()) {
138 				    	fragment.getAudio().deleteOnExit();
139 					}
140 		 		}
141 
142 		        soundFactory.read(audio);
143 	        }
144 
145 		    progressMonitor.setProgress(++progressCounter);
146 
147  			if (progressMonitor.isCanceled()) {
148  				return null;
149  			}
150 
151 		    for (Frame frame: fragment.getFrames()) {
152 	 			if (progressMonitor.isCanceled()) {
153 	 				return null;
154 	 			}
155 
156 	 			boolean addStop = false;
157 
158 	 			++frameNo;
159 
160 		        if (firstFrame) {
161 		        	firstFrame = false;
162 
163 		            MovieHeader header = new MovieHeader();
164 		            header.setCompressed(true);
165 		            header.setFrameRate(source.getFramesPerSecond());
166 
167 		        	int toolbarHeight = 29 * 20;
168 		        	int toolbarWidth = 495 * 20;
169 
170 		        	int toolbarX = 0; // - image.getWidth()*20/2;
171 		        	int toolbarY = frame.getSize().height*20;
172 
173 					int movieWidth = frame.getSize().width*20;
174 					int movieHeight = frame.getSize().height*20;
175 					if (config.isToolBar()) {
176 						movieHeight+=toolbarHeight;
177 					}
178 
179 					ret = new Dimension(movieWidth/20, movieHeight/20);
180 
181 		        	float toolbarScaleX = (float) movieWidth / (float) toolbarWidth;
182 		        	float toolbarScaleY = 1.0f;
183 
184 					Bounds movieBounds = new Bounds(0, 0, movieWidth, movieHeight);
185 					header.setFrameSize(movieBounds);
186 		            movie.add(header);
187 		            movie.add(new Background(WebPalette.WHITE.color()));
188 
189 		            if (config.isToolBar()) {
190 			        	// Add all the shapes etc used for buttons
191 			        	List<DefineTag> toolbarDefinitions = manager.getDefinitions();
192 						movie.getObjects().addAll(toolbarDefinitions);
193 
194 			        	Place2 placeBackground = manager.getButton("background", layer++, 0, 0);
195 			        	placeBackground.setTransform(new CoordTransform(toolbarScaleX, toolbarScaleY, 0, 0, toolbarX, toolbarY));
196 
197 			        	// Get the button to use and give its position
198 			        	movie.add(placeBackground);
199 			        	movie.add(manager.getButton("play_button", layer++, toolbarX + 500, toolbarY + toolbarHeight / 2));
200 			        	movie.add(manager.getButton("progress_bar", layer++, toolbarX + 1000, toolbarY + toolbarHeight / 2));
201 			        	movie.add(manager.getButton("volume_control", layer++, toolbarX + 5600, toolbarY + toolbarHeight / 2));
202 
203 			        	if (!config.isPlay()) {
204 			        		addStop = true;
205 			        	}
206 		            }
207 		        }
208 
209 		        if (!soundHeaderAdded && soundFactory!=null) {
210 		            movie.add(soundFactory.streamHeader(source.getFramesPerSecond()));
211 		            soundHeaderAdded = true;
212 		        }
213 
214 		        if (soundFactory!=null) {
215 			        MovieTag soundBlock = soundFactory.streamSound();
216 			        if (soundBlock != null) {
217 			            movie.add(soundBlock);
218 			        }
219 		        }
220 
221 		 		for (Shape shape: frame.getShapes()) {
222 		 			if (shape.getContent().coversEverything() || imgLayer==maxImgLayer) {
223 		 				for (int i=minImgLayer; i<=imgLayer; ++i) {
224 		 					movie.add(new Remove2(i));
225 		 				}
226 		 				imgLayer = minImgLayer;
227 		 			}
228 
229 		 			ShapeContent shapeContent = shape.getContent();
230 		 			Image image;
231 		 			if (shapeContent instanceof Image) {
232 		 				image = (Image) shapeContent;
233 		 			} else if (shape.getContent() instanceof ImageReference) {
234 		 				image = ((ImageReference) shapeContent).getImage();
235 		 			} else {
236 		 				throw new IllegalArgumentException("Unexpected content type: "+shapeContent);
237 		 			}
238 
239 		 			ImageTag imageTag = imageCache.get(image);
240 		 			if (imageTag==null) {
241 		 				try {
242 				            ImageDecoder decoder = ImageRegistry.getImageProvider("image/"+config.getImageFormat().toLowerCase());
243 				            decoder.read(new ByteArrayInputStream(image.getImage().getImageBytes()));
244 				            imageTag = decoder.defineImage(uid.incrementAndGet());
245 				            imageCache.put(image, imageTag);
246 				            movie.add(imageTag);
247 		 				} catch (Exception e) {
248 		 					// Doing our best to create movie, even with flaws.
249 		 					System.err.println("Error encoding image at frame "+frameNo+": "+e);
250 		 					e.printStackTrace();
251 		 					if (JOptionPane.showConfirmDialog(config.getParentComponent(),
252 		 							"Error encoding image ("+image.getImage().getWidth()+"*"+image.getImage().getHeight()+") at frame "+frameNo+": "+e+". Continue encoding?",
253 		 							"Encoding error",
254 	                                JOptionPane.YES_NO_OPTION,
255 	                                JOptionPane.ERROR_MESSAGE)==JOptionPane.NO_OPTION) {
256 		 						throw e;
257 		 					}
258 		 				}
259 		 			}
260 
261 		 			int shapeId = uid.incrementAndGet();
262 			        DefineTag shapeTag = new ImageShape().defineShape(shapeId, imageTag);
263 			        Place2 place = Place2.show(shapeTag.getIdentifier(), imgLayer++, shape.getLocation().x*20, shape.getLocation().y*20);
264 		            movie.add(shapeTag);
265 		            movie.add(place);
266 		 		}
267 
268 		        Point mouseLocation = frame.getMousePointer();
269 		        if (mouseLocation!=null) {
270 	        		if (mouseImage==null) {
271 	        			BufferedImage mouseBi = ImageIO.read(getClass().getResource("mouse.png"));
272 	        	    	if (config.getScreenScale()<0.99 || config.getScreenScale() > 1.01) {
273 	        		    	BufferedImage scaled = new BufferedImage((int) (mouseBi.getWidth()*config.getScreenScale()), (int) (mouseBi.getHeight()*config.getScreenScale()), mouseBi.getType());
274 	        		    	Graphics2D g = scaled.createGraphics();
275 	        		    	g.setComposite(AlphaComposite.Src);
276 	        		    	g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
277 	        		    	g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
278 	        		    	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
279 	        		    	g.drawImage(mouseBi, 0, 0, scaled.getWidth(), scaled.getHeight(), null);
280 	        		    	g.dispose();
281 	        		    	mouseBi = scaled;
282 	        	    	}
283 
284 	    				ByteArrayOutputStream baos = new ByteArrayOutputStream();
285 	    				ImageIO.write(mouseBi, "PNG", baos);
286 	    	        	baos.close();
287 	    	            ImageDecoder decoder = ImageRegistry.getImageProvider("image/png");
288 	    	            decoder.read(new ByteArrayInputStream(baos.toByteArray()));
289 		            	mouseImage = decoder.defineImage(uid.incrementAndGet());
290 		            	movie.add(mouseImage);
291 	        		}
292 
293 	        		if (!mouseLocation.equals(prevMouseLocation)) {
294 	        			prevMouseLocation = mouseLocation;
295 			            mouseUid = uid.incrementAndGet();
296 				        DefineTag mShape = new ImageShape().defineShape(uid.incrementAndGet(), mouseImage); //createRect(mouseUid, 100, 100, WebPalette.RED.color());
297 				        if (mousePlace==null) {
298 				        	mousePlace = Place2.show(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20);
299 				        } else {
300 				        	mousePlace = Place2.replace(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20);
301 				        }
302 			            movie.add(mShape);
303 			            movie.add(mousePlace);
304 	        		}
305 	        	} else if (mouseUid!=-1) {
306 	        		Remove remove = new Remove(mouseUid, mouseLayer);
307 	        		movie.add(remove);
308 	        	}
309 
310 		        if (addStop) {
311 		        	DoAction cmd = new DoAction(new ArrayList<Action>());
312 		        	cmd.add(BasicAction.STOP);
313 		        	movie.add(cmd);
314 		        }
315 	        	movie.add(ShowFrame.getInstance());
316 
317 	 			progressMonitor.setProgress(++progressCounter);
318 		    }
319 
320 	 		progressMonitor.setProgress(++progressCounter);
321 	        if (soundFactory!=null) {
322 		 		progressMonitor.setNote("Recording trailing sound");
323 		        MovieTag block;
324 		        while ((block = soundFactory.streamSound()) != null) {
325 		            movie.add(block);
326 		            movie.add(ShowFrame.getInstance());
327 		        }
328 	        }
329 
330 	        if (audio!=null) {
331 	        	if (!audio.delete()) {
332 	        		audio.deleteOnExit();
333 	        	}
334 	        }
335         }
336 
337         if (!config.isLoop()) {
338         	List<Action> actions = new ArrayList<Action>();
339         	actions.add(BasicAction.STOP);
340         	actions.add(BasicAction.END);
341 			DoAction doAction = new DoAction(actions);
342 			movie.add(doAction);
343             movie.add(ShowFrame.getInstance());
344         }
345 
346         progressMonitor.setProgress(++progressCounter);
347         progressMonitor.setNote("Encoding movie");
348 		movie.encodeToStream(out);
349 		source.close();
350 		return ret;
351 	}
352 
353 }
354