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