View Javadoc
1   /******************************************************************************
2    * FourthRibbon.java - Multithreaded Mosaic
3    * 
4    * PicMan - The BuckoSoft Picture Manager in Java
5    * Copyright(c) 2014 - Dick Balaska
6    * 
7    */
8   package com.buckosoft.PicMan.business.mosaic.engine;
9   
10  import java.awt.Graphics2D;
11  import java.awt.image.BufferedImage;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.concurrent.CountDownLatch;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  
19  import com.buckosoft.PicMan.business.mosaic.MosaicEngine;
20  import com.buckosoft.PicMan.domain.MosaicTile;
21  import com.buckosoft.PicMan.domain.Pic;
22  import com.buckosoft.PicMan.domain.Thumbnail;
23  
24  /** Rework {@link ThirdRibbon} with multithreadedness
25   * @author Dick Balaska
26   * @since 2014/02/08
27   * @see <a href="http://cvs.buckosoft.com/Projects/java/PicMan/PicMan/src/main/java/com/buckosoft/PicMan/business/mosaic/engine/FourthRibbon.java">FourthRibbon.java</a>
28   * @see SecondRibbon
29   */
30  public class FourthRibbon extends MosaicEngine {
31  	private static final boolean DEBUG = false;
32  	private static final boolean DEBUGDRAW = false;
33  	private final Log logger = LogFactory.getLog(getClass());
34  
35  	private	int			curRow = 0;
36  	private	int[]		curColsL;
37  	private	int[]		curColsR;
38  	private	int[]		pixels;		// master pic pixels
39  	private	int			curCol;
40  	private	int			maxRow;
41  	private	int			maxCol;
42  	private	int			startRow;
43  	private	int			p;
44  	private	String		curPicName;
45  	private	List<Pic>	picList;
46  	private	HashMap<String, Double>	rateMap;
47  	private	String		info = "N/A";
48  	private	int[]		used;
49  	private	int			tileRows;
50  	private	String		buildStep = "Idle";
51  	private	int			pass = -1;
52  	private	long[][]	rateL;
53  	private	long[][]	rateR;
54  	private	Graphics2D	gd;
55  	private	int 		baseX;
56  	private	int			baseY;
57  	private	double		rateWeight = 1.0;
58  	private	double		usedWeight = 0.0;
59  	private	boolean		workDoneL = false;
60  	private	boolean		workDoneR = false;
61  	private	int			reduceStyle = 0;	// 0 = subtract in the calc, 1 = subtract in the draw
62  
63  	/** Default constructor	 */
64  	public FourthRibbon() {
65  		addConfig("baseX", "X offset of the base point", ConfigItem.TYPE_INT, 0);
66  		addConfig("baseY", "Y offset of the base point", ConfigItem.TYPE_INT, 0);
67  		addConfig("usedWeight", "Multiplier to reduce used pics", ConfigItem.TYPE_DOUBLE, 1.0);
68  		addConfig("reduceStyle", "Reduce: 0=calc, 1=draw", ConfigItem.TYPE_INT, 1);
69  		addConfig("threads", "Threads: (" + Runtime.getRuntime().availableProcessors() + ") max", ConfigItem.TYPE_INT, 1);
70  	}
71  
72  	/** Return our current build status
73  	 * @return The status
74  	 */
75  	public	String	getStatus() {
76  		StringBuilder sb = new StringBuilder();
77  		sb.append(buildStep);
78  		sb.append(": pass=");
79  		sb.append(pass);
80  		if (workDoneL || workDoneR)
81  			sb.append(" ");
82  		if (workDoneL)
83  			sb.append("L");
84  		if (workDoneR)
85  			sb.append("R");
86  		if (!buildStep.equals("Calc")) {
87  			sb.append(" curRow = ");
88  			sb.append(curRow);
89  			sb.append(" curCol = ");
90  			sb.append(curCol);
91  		}
92  		sb.append(" p=");
93  		sb.append(p);
94  		return(sb.toString());
95  	}
96  
97  	/** Return a semi-static info string
98  	 * @return "tileRows= y maxCol= x pics=n
99  	 */
100 	public String getInfo() {
101 		return(info);
102 	}
103 
104 	protected boolean _build() {
105 		jobLogEntry.setNote("Setup");
106 		gd = bi.createGraphics();
107 		baseX = (Integer)buildParameters.get("baseX");
108 		baseY = (Integer)buildParameters.get("baseY");
109 		rateWeight = (Double)buildParameters.get("rateWeight");
110 		usedWeight = (Double)buildParameters.get("usedWeight");
111 		reduceStyle = (Integer)buildParameters.get("reduceStyle");
112 		
113 		picList = dbf.getPics(this.mosaicSet, tileHeight);
114 		rateMap = new HashMap<String, Double>();
115 		for (Pic pic : picList) {
116 			double d = dbf.getPicRate(pic.getName(), this.mosaicSet, tileHeight);
117 			rateMap.put(pic.getName(), new Double(d));
118 			//if (DEBUG)
119 			//	logger.info("Pic " + pic.getName() + ": rate: " + d);
120 		}
121 		if (DEBUG)
122 			logger.info("Working from " + picList.size() + " pics");
123 		startRow = 0 - (baseY % tileHeight);
124 		startRow = baseY % tileHeight;
125 		if (startRow > 0)
126 			startRow -= tileHeight;
127 		maxRow = this.masterPic.getHeight();
128 		maxCol = this.masterPic.getWidth();
129 		pixels = bi.getRGB(0, 0, maxCol, maxRow, null, 0, maxCol);
130 		tileRows = maxRow / tileHeight;
131 		if (tileRows * tileHeight + startRow < maxRow)
132 			tileRows++;
133 		if (tileRows * tileHeight + startRow < maxRow)
134 			tileRows++;
135 		curColsL = new int[tileRows];
136 		curColsR = new int[tileRows];
137 		for (int i=0; i<tileRows; i++) {
138 			curColsL[i] = baseX;
139 			curColsR[i] = baseX;
140 		}
141 		rateL = new long[tileRows][picList.size()];		// rate each pic for this location
142 		rateR = new long[tileRows][picList.size()];		// rate each pic for this location
143 		used = new int[picList.size()];					// number of times each pic is used
144 		info = "tileRows=" + tileRows + " startRow=" + startRow + " pics=" + picList.size();
145 		if (DEBUG)
146 			logger.info("tileRows=" + tileRows + " rowHeight=" + tileHeight);
147 		if (baseX > maxCol)
148 			baseX = maxCol;
149 		if (baseY > maxRow)
150 			baseY = maxRow;
151 
152 	
153 		BufferedImage mbi;
154 		workDoneL = true;
155 		workDoneR = true;
156 		while (workDoneL || workDoneR) {
157 			pass++;
158 			jobLogEntry.setNote("pass: " + pass);
159 			if (!pmf.isEngineOn())
160 				return(false);
161 			buildStep = "Calc";
162 			for (p=0; p<picList.size(); p++) {
163 				curPicName = picList.get(p).getName();
164 				//if (DEBUG)
165 				//	logger.info("reading '" + curPicName + "'");
166 				Pic xp = picList.get(p);
167 				Thumbnail xtn = pmf.getMosaicThumbNail(xp, tileHeight);
168 				mbi = xtn.getImage();
169 //				mbi = pmf.getMosaicThumbNail(picList.get(p), tileHeight).getImage();
170 				CountDownLatch latch = new CountDownLatch(2);
171 				if (workDoneL)
172 					new Thread(new CalcLeft(latch, mbi)).start();
173 				else
174 					latch.countDown();
175 				if (workDoneR)
176 					new Thread(new CalcRight(latch, mbi)).start();
177 				else
178 					latch.countDown();
179 				try {
180 					latch.await();
181 				} catch (InterruptedException e) {
182 					logger.warn(e);
183 				}
184 				if (!workDoneL && !workDoneR)
185 					break;
186 			}
187 			buildStep = "Draw";
188 			if (workDoneL)
189 				workDoneL = _drawLeft();
190 			if (workDoneR)
191 				workDoneR = _drawRight();
192 			if (!mosaic.isBatch())
193 				dbf.storeMosaic(mosaic);
194 		}
195 
196 		buildStep = "Done";
197 		return(false);
198 	}
199 
200 	class CalcLeft implements Runnable {
201 		CountDownLatch latch;
202 		BufferedImage mbi;
203 
204 		CalcLeft(CountDownLatch latch, BufferedImage mbi) {
205 			this.latch = latch;
206 			this.mbi = mbi;
207 		}
208 		@Override
209 		public void run() {
210 			_calcLeft();
211 			latch.countDown();
212 		}
213 
214 		private boolean _calcLeft() {
215 			boolean workDone = false;
216 			int	x,y;
217 			int mw = mbi.getWidth();
218 			int mh = mbi.getHeight();
219 			for (int curRow=0; curRow<tileRows; curRow++) {
220 				int curCol = curColsL[curRow];
221 				int	cr = curRow * tileHeight + startRow;
222 				if (curCol > 0) {
223 					workDone = true;
224 					if (reduceStyle == 0)
225 						rateL[curRow][p] = -(long)(used[p]*usedWeight);
226 					else
227 						rateL[curRow][p] = 0;
228 					for (x=0; x<mw; x++) {
229 						y=0;
230 						if (cr<0)
231 							y = -startRow;
232 						for (; y<mh; y++) {
233 							int mpx = mbi.getRGB(x, y);
234 							int	ppx;
235 							if (curCol+x-mw < 0 || y+cr >= maxRow)
236 								ppx = mpx;
237 							else
238 								//ppx = bi.getRGB(x+curCol-mw, y+cr);
239 								ppx = pixels[x+curCol-mw+((y+cr)*maxCol)];
240 	//						rate[curRow][p] += Math.abs(((mpx)&0xFF)     - ((ppx)&0xFF))     * 30;
241 	//						rate[curRow][p] += Math.abs(((mpx>>8)&0xFF)  - ((ppx>>8)&0xFF))  * 59;
242 	//						rate[curRow][p] += Math.abs(((mpx>>16)&0xFF) - ((ppx>>16)&0xFF)) * 11;
243 							rateL[curRow][p] += Math.abs(((mpx)&0xFF)     - ((ppx)&0xFF))     * 1;
244 							rateL[curRow][p] += Math.abs(((mpx>>8)&0xFF)  - ((ppx>>8)&0xFF))  * 1;
245 							rateL[curRow][p] += Math.abs(((mpx>>16)&0xFF) - ((ppx>>16)&0xFF)) * 1;
246 						}
247 					}
248 					// Portrait always wins over landscape because there is less pixels to go wrong.
249 					// Give equal weight per pixel column
250 					rateL[curRow][p] /= mw;
251 					//rateL[curRow][p] *= ((1+rateWeight) * (9-rateMap.get(curPicName)));
252 					rateL[curRow][p] *= (1+(rateWeight * (9-rateMap.get(curPicName))));
253 					//logger.info("rate[" + p + "]=" + rate[p] + " ("+ picList.get(p).getName() + ")");
254 				}
255 			}
256 			workDoneL = workDone;
257 			return(workDone);
258 		}
259 
260 	}
261 	
262 	class CalcRight implements Runnable {
263 		CountDownLatch latch;
264 		BufferedImage mbi;
265 
266 		CalcRight(CountDownLatch latch, BufferedImage mbi) {
267 			this.latch = latch;
268 			this.mbi = mbi;
269 		}
270 		@Override
271 		public void run() {
272 			_calcRight();
273 			latch.countDown();
274 		}
275 
276 		private boolean _calcRight() {
277 			boolean workDone = false;
278 			int mw = mbi.getWidth();
279 			int mh = mbi.getHeight();
280 			int	x,y;
281 			for (int curRow=0; curRow<tileRows; curRow++) {
282 				int curCol = curColsR[curRow];
283 				int	cr = curRow * tileHeight + startRow;
284 				if (curCol < maxCol) {
285 					workDone = true;
286 					if (reduceStyle == 0)
287 						rateR[curRow][p] = -(long)(used[p]*usedWeight);
288 					else
289 						rateR[curRow][p] = 0;
290 					for (x=0; x<mw; x++) {
291 						y=0;
292 						if (cr<0)
293 							y = -startRow;
294 						for (; y<mh; y++) {
295 							int mpx = mbi.getRGB(x, y);
296 							int	ppx;
297 							if (x+curCol >= maxCol || y+cr >= maxRow)
298 								ppx = mpx;
299 							else
300 								// ppx = bi.getRGB(x+curCol, y+cr);
301 								ppx = pixels[x+curCol+((y+cr)*maxCol)];
302 	//						rate[curRow][p] += Math.abs(((mpx)&0xFF)     - ((ppx)&0xFF))     * 30;
303 	//						rate[curRow][p] += Math.abs(((mpx>>8)&0xFF)  - ((ppx>>8)&0xFF))  * 59;
304 	//						rate[curRow][p] += Math.abs(((mpx>>16)&0xFF) - ((ppx>>16)&0xFF)) * 11;
305 							rateR[curRow][p] += Math.abs(((mpx)&0xFF)     - ((ppx)&0xFF))     * 1;
306 							rateR[curRow][p] += Math.abs(((mpx>>8)&0xFF)  - ((ppx>>8)&0xFF))  * 1;
307 							rateR[curRow][p] += Math.abs(((mpx>>16)&0xFF) - ((ppx>>16)&0xFF)) * 1;
308 						}
309 					}
310 					// Portrait always wins over landscape because there is less pixels to go wrong.
311 					// Give equal weight per pixel column
312 					rateR[curRow][p] /= mw;
313 					//rateR[curRow][p] *= (1+(rateWeight * rateMap.get(curPicName)));
314 					rateR[curRow][p] *= (1+(rateWeight * (9-rateMap.get(curPicName))));
315 				}
316 			}
317 			workDoneR = workDone;
318 			return(workDone);
319 		}
320 	}
321 
322 	private boolean _drawLeft() {
323 		boolean workDone = false;
324 		BufferedImage bi;
325 		int	x;
326 		for (curRow=0; curRow<tileRows; curRow++) {
327 			curCol = curColsL[curRow];
328 			int cr = curRow * tileHeight + startRow;
329 			if (curCol < 0)
330 				continue;
331 			long best = Long.MAX_VALUE;
332 			int	besti = 0;
333 			int	besti2 = 0;
334 			for (x=0; x<rateL[curRow].length; x++) {
335 				if (rateL[curRow][x] < best) {
336 					besti2 = besti;
337 					best = rateL[curRow][x];
338 					besti = x;
339 				}
340 			}
341 			//p = besti;
342 			if (DEBUGDRAW) {
343 				if (besti == -1)
344 					logger.info("row=" + curRow + " besti == -1");
345 				else
346 					logger.info("besti = " + besti + " (" + picList.get(besti).getName() + ") = " + rateL[curRow][besti]);
347 				if (besti2 == -1)
348 					logger.info("row=" + curRow + " besti2 == -1");
349 				else
350 					logger.info("besti2 = " + besti2 + " (" + picList.get(besti2).getName() + ") = " + rateL[curRow][besti2]);
351 			}
352 			used[besti]++;
353 			bi = pmf.getMosaicThumbNail(picList.get(besti), tileHeight).getImage();
354 			if (!mosaic.isBatch()) {
355 				MosaicTile	tile = new MosaicTile(this.mid, picList.get(besti).getPid(), curCol-maxCol, cr, maxCol, maxRow);
356 				dbf.storeMosaicTile(tile);
357 			}
358 			gd.drawImage(bi, null, curCol-maxCol, cr);
359 			workDone = true;
360 			curColsL[curRow] -= maxCol;
361 			if (reduceStyle == 1) {
362 				String name = picList.get(besti).getName();
363 				double dd = rateMap.get(name) - (rateMap.get(name) * this.usedWeight);
364 				rateMap.put(name, dd);
365 			}
366 			setLastMosaicUpdate();
367 		}
368 		return(workDone);
369 	}
370 
371 	private boolean _drawRight() {
372 		boolean workDone = false;
373 		BufferedImage bi;
374 		int	x;
375 		for (curRow=0; curRow<tileRows; curRow++) {
376 			curCol = curColsR[curRow];
377 			int cr = curRow * tileHeight + startRow;
378 			if (curCol >= maxCol)
379 				continue;
380 			long best = Long.MAX_VALUE;
381 			int	besti = 0;
382 			int	besti2 = 0;
383 			for (x=0; x<rateR[curRow].length; x++) {
384 				if (rateR[curRow][x] < best) {
385 					besti2 = besti;
386 					best = rateR[curRow][x];
387 					besti = x;
388 				}
389 			}
390 			//p = besti;
391 			if (DEBUGDRAW) {
392 				if (besti == -1)
393 					logger.info("row=" + curRow + " besti == -1");
394 				else
395 					logger.info("besti = " + besti + " (" + picList.get(besti).getName() + ") = " + rateR[curRow][besti]);
396 				if (besti2 == -1)
397 					logger.info("row=" + curRow + " besti2 == -1");
398 				else
399 					logger.info("besti2 = " + besti2 + " (" + picList.get(besti2).getName() + ") = " + rateR[curRow][besti2]);
400 			}
401 			used[besti]++;
402 			bi = pmf.getMosaicThumbNail(picList.get(besti), tileHeight).getImage();
403 			if (!mosaic.isBatch()) {
404 				MosaicTile	tile = new MosaicTile(this.mid, picList.get(besti).getPid(), curCol, cr, maxCol, maxRow);
405 				dbf.storeMosaicTile(tile);
406 			}
407 			gd.drawImage(bi, null, curCol, cr);
408 			workDone = true;
409 			curColsR[curRow] += maxCol;
410 			if (reduceStyle == 1) {
411 				String name = picList.get(besti).getName();
412 				double dd = rateMap.get(name) - (rateMap.get(name) * this.usedWeight);
413 				rateMap.put(name, dd);
414 			}
415 			setLastMosaicUpdate();
416 		}
417 		return(workDone);
418 	}
419 }