I have this .java datafile. The data file is a part of an imagej plugin.
The whole data structure is here:
enter link description here
package mosaic.plugins;
import ij.IJ;
import ij.ImagePlus;
import ij.macro.Interpreter;
import ij.measure.ResultsTable;
import ij.process.ByteProcessor;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.WindowConstants;
import mosaic.plugins.utils.PlugIn8bitBase;
import net.imglib2.Cursor;
import net.imglib2.IterableInterval;
import net.imglib2.RandomAccess;
import net.imglib2.img.ImagePlusAdapter;
import net.imglib2.img.Img;
import net.imglib2.img.ImgFactory;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.NumericType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.IntervalView;
import net.imglib2.view.Views;
public class Naturalization extends PlugIn8bitBase
{
// Precision in finding your best T
private static final float EPS = 0.0001f;
// Prior parameter for first oder
// In this case is for all channels
// Fixed parameter
private static final float T1_pr = 0.3754f;
// Number of bins for the Laplacian Histogram
// In general is 4 * N_Grad
// max of laplacian value is 4 * 255
private static final int N_Lap = 2041;
// Offset shift in the histogram bins
// Has to be N_Lap / 2;
private static final int Lap_Offset = 1020;
// Number of bins for the Gradient
private static final int N_Grad = 512;
// Offset for the gradient histogram shift
private static final int Grad_Offset = 256;
// Prior parameter for second order (Parameters learned from trained data set)
// For different color R G B
// For one channel image use an average of them
private final float T2_pr[] = {0.2421f ,0.2550f, 0.2474f, 0.24816666f};
// Keeps values of PSNR for all images and channels in case of RGB. Maps: imageNumber -> map (channel, PSNR value)
private final Map<Integer, Map<Integer, Float>> iPsnrOutput = new TreeMap<Integer, Map<Integer, Float>>();
private synchronized void addPsnr(int aSlice, int aChannel, float aValue) {
Map<Integer, Float> map = iPsnrOutput.get(aSlice);
boolean isNewMap = false;
if (map == null) {
map = new TreeMap<Integer, Float>();
isNewMap = true;
}
map.put(aChannel, aValue);
if (isNewMap) {
iPsnrOutput.put(aSlice, map);
}
}
#Override
protected void processImg(ByteProcessor aOutputImg, ByteProcessor aOrigImg, int aChannelNumber) {
// perform naturalization
final ImagePlus naturalizedImg = naturalize8bitImage(aOrigImg, aChannelNumber);
// set processed pixels to output image
aOutputImg.setPixels(naturalizedImg.getProcessor().getPixels());
}
#Override
protected void postprocessBeforeShow() {
// Create result table with all stored PSNRs.
final ResultsTable rs = new ResultsTable();
for (final Entry<Integer, Map<Integer, Float>> e : iPsnrOutput.entrySet()) {
rs.incrementCounter();
for (final Entry<Integer, Float> m : e.getValue().entrySet()) {
switch(m.getKey()) {
case CHANNEL_R: rs.addValue("Naturalization R", m.getValue()); rs.addValue("Estimated R PSNR", calculate_PSNR(m.getValue())); break;
case CHANNEL_G: rs.addValue("Naturalization G", m.getValue()); rs.addValue("Estimated G PSNR", calculate_PSNR(m.getValue())); break;
case CHANNEL_B: rs.addValue("Naturalization B", m.getValue()); rs.addValue("Estimated B PSNR", calculate_PSNR(m.getValue())); break;
case CHANNEL_8G: rs.addValue("Naturalization", m.getValue()); rs.addValue("Estimated PSNR", calculate_PSNR(m.getValue())); break;
default: break;
}
}
}
if (!Interpreter.isBatchMode()) {
rs.show("Naturalization and PSNR");
showMessage();
}
}
private ImagePlus naturalize8bitImage(ByteProcessor imp, int aChannelNumber) {
Img<UnsignedByteType> TChannel = ImagePlusAdapter.wrap(new ImagePlus("", imp));
final float T2_prior = T2_pr[(aChannelNumber <= CHANNEL_B) ? 2-aChannelNumber : CHANNEL_8G];
final float[] result = {0.0f}; // ugly but one of ways to get result back via parameters;
// Perform naturalization and store PSNR result. Finally return image in ImageJ format.
TChannel = performNaturalization(TChannel, T2_prior, result);
addPsnr(imp.getSliceNumber(), aChannelNumber, result[0]);
return ImageJFunctions.wrap(TChannel,"temporaryName");
}
/**
* Naturalize the image
* #param Img original image
* #param Theta parameter
* #param Class<T> Original image
* #param Class<S> Calculation Type
* #param T2_prior Prior to use
* #param result One element array to store nautralization factor
*/
private <T extends NumericType<T> & NativeType<T> & RealType<T>, S extends RealType<S>> Img<T> doNaturalization(Img<T> image_orig, S Theta,Class<T> cls_t, float T2_prior, float[] result) throws InstantiationException, IllegalAccessException
{
if (image_orig == null) {return null;}
// Check that the image data set is 8 bit
// Otherwise return an error or hint to scale down
final T image_check = cls_t.newInstance();
final Object obj = image_check;
if (!(obj instanceof UnsignedByteType)) {
IJ.error("Error it work only with 8-bit type");
return null;
}
final float Nf = findNaturalizationFactor(image_orig, Theta, T2_prior);
result[0] = Nf;
final Img<T> image_result = naturalizeImage(image_orig, Nf, cls_t);
return image_result;
}
private <S extends RealType<S>, T extends NumericType<T> & NativeType<T> & RealType<T>>
Img<T> naturalizeImage(Img<T> image_orig, float Nf, Class<T> cls_t)
throws InstantiationException, IllegalAccessException
{
// Mean of the original image
// S mean_original = cls_s.newInstance();
// Mean<T,S> m = new Mean<T,S>();
// m.compute(image_orig.cursor(), mean_original);
// TODO: quick fix for deprecated code above. Is new 'mean' utility introduced in imglib2?
float mean_original = 0.0f;
final Cursor<T> c2 = image_orig.cursor();
float count = 0.0f;
while (c2.hasNext()) {
c2.next();
mean_original += c2.get().getRealFloat();
count += 1.0f;
}
mean_original /= count;
// Create result image
final long[] origImgDimensions = new long[2];
image_orig.dimensions(origImgDimensions);
final Img<T> image_result = image_orig.factory().create(origImgDimensions, cls_t.newInstance());
// for each pixel naturalize
final Cursor<T> cur_orig = image_orig.cursor();
final Cursor<T> cur_ir = image_result.cursor();
while (cur_orig.hasNext()) {
cur_orig.next();
cur_ir.next();
final float tmp = cur_orig.get().getRealFloat();
// Naturalize
float Nat = (int) ((tmp - mean_original)*Nf + mean_original + 0.5);
if (Nat < 0)
{Nat = 0;}
else if (Nat > 255)
{Nat = 255;}
cur_ir.get().setReal(Nat);
}
return image_result;
}
private <S extends RealType<S>, T extends NumericType<T> & NativeType<T> & RealType<T>> float findNaturalizationFactor(Img<T> image_orig, S Theta, float T2prior) {
final ImgFactory<FloatType> imgFactoryF = new ArrayImgFactory<FloatType>();
// Create one dimensional image (Histogram)
final Img<FloatType> LapCDF = imgFactoryF.create(new long[] {N_Lap}, new FloatType());
// Two dimensional image for Gradient
final Img<FloatType> GradCDF = imgFactoryF.create(new long[] {N_Grad, 2}, new FloatType());
// GradientCDF = Integral of the histogram of the of the Gradient field
// LaplacianCDF = Integral of the Histogram of the Laplacian field
final Img<FloatType> GradD = create2DGradientField();
calculateLaplaceFieldAndGradient(image_orig, LapCDF, GradD);
convertGrad2dToCDF(GradD);
calculateGradCDF(GradCDF, GradD);
calculateLapCDF(LapCDF);
// For each channel find the best T1
// EPS=precision
// for X component
float T_tmp = (float)FindT(Views.iterable(Views.hyperSlice(GradCDF, GradCDF.numDimensions()-1 , 0)), N_Grad, Grad_Offset, EPS);
// for Y component
T_tmp += FindT(Views.iterable(Views.hyperSlice(GradCDF, GradCDF.numDimensions()-1 , 1)), N_Grad, Grad_Offset, EPS);
// Average them and divide by the prior parameter
final float T1 = T_tmp/(2*T1_pr);
// Find the best parameter and divide by the T2 prior
final float T2 = (float)FindT(LapCDF, N_Lap, Lap_Offset, EPS)/T2prior;
// Calculate naturalization factor!
final float Nf = (float) ((1.0-Theta.getRealDouble())*T1 + Theta.getRealDouble()*T2);
return Nf;
}
/**
* Calculate the peak SNR from the Naturalization factor
*
* #param Nf naturalization factor
* #return the PSNR
*/
String calculate_PSNR(double x)
{
if (x >= 0 && x <= 0.934)
{
return String.format("%.2f", new Float(23.65 * Math.exp(0.6 * x) - 20.0 * Math.exp(-7.508 * x)));
}
else if (x > 0.934 && x < 1.07)
{
return new String("> 40");
}
else if (x >= 1.07 && x < 1.9)
{
return String.format("%.2f", new Float(-11.566 * x + 52.776));
}
else
{
return String.format("%.2f",new Float(13.06*x*x*x*x - 121.4 * x*x*x + 408.5 * x*x -595.5*x + 349));
}
}
private Img<UnsignedByteType> performNaturalization(Img<UnsignedByteType> channel, float T2_prior, float[] result) {
// Parameters balance between first order and second order
final FloatType Theta = new FloatType(0.5f);
try {
channel = doNaturalization(channel, Theta, UnsignedByteType.class, T2_prior, result);
} catch (final InstantiationException e) {
e.printStackTrace();
} catch (final IllegalAccessException e) {
e.printStackTrace();
}
return channel;
}
// Original data
// N = nuber of bins
// offset of the histogram
// T current
private double FindT_Evalue(float[] p_d, int N, int offset, float T)
{
double error = 0;
for (int i=-offset; i<N-offset; ++i) {
final double tmp = Math.atan(T*(i)) - p_d[i+offset];
error += (tmp*tmp);
}
return error;
}
// Find the T
// data CDF Histogram
// N number of bins
// Offset of the histogram
// eps precision
private double FindT(IterableInterval<FloatType> data, int N, int OffSet, float eps)
{
//find the best parameter between data and model atan(Tx)/pi+0.5
// Search between 0 and 1.0
float left = 0;
float right = 1.0f;
float m1 = 0.0f;
float m2 = 0.0f;
// Crate p_t to save computation (shift and rescale the original CDF)
final float p_t[] = new float[N];
// Copy the data
final Cursor<FloatType> cur_data = data.cursor();
for (int i = 0; i < N; ++i)
{
cur_data.next();
p_t[i] = (float) ((cur_data.get().getRealFloat() - 0.5)*Math.PI);
}
// While the precision is bigger than eps
while (right-left>=eps)
{
// move left and right of 1/3 (m1 and m2)
m1=left+(right-left)/3;
m2=right-(right-left)/3;
// Evaluate on m1 and m2, ane move the extreme point
if (FindT_Evalue(p_t, N, OffSet, m1) <=FindT_Evalue(p_t, N, OffSet, m2)) {
right=m2;
}
else {
left=m1;
}
}
// return the average
return (m1+m2)/2;
}
private Img<FloatType> create2DGradientField() {
final long dims[] = new long[2];
dims[0] = N_Grad;
dims[1] = N_Grad;
final Img<FloatType> GradD = new ArrayImgFactory<FloatType>().create(dims, new FloatType());
return GradD;
}
private void calculateLapCDF(Img<FloatType> LapCDF) {
final RandomAccess<FloatType> Lap_hist2 = LapCDF.randomAccess();
//convert Lap to CDF
for (int i = 1; i < N_Lap; ++i)
{
Lap_hist2.setPosition(i-1,0);
final float prec = Lap_hist2.get().getRealFloat();
Lap_hist2.move(1,0);
Lap_hist2.get().set(Lap_hist2.get().getRealFloat() + prec);
}
}
private void calculateGradCDF(Img<FloatType> GradCDF, Img<FloatType> GradD) {
final RandomAccess<FloatType> Grad_dist = GradD.randomAccess();
// Gradient on x pointer
final IntervalView<FloatType> Gradx = Views.hyperSlice(GradCDF, GradCDF.numDimensions()-1 , 0);
// Gradient on y pointer
final IntervalView<FloatType> Grady = Views.hyperSlice(GradCDF, GradCDF.numDimensions()-1 , 1);
integrateOverRowAndCol(Grad_dist, Gradx, Grady);
scaleGradiens(Gradx, Grady);
}
private void scaleGradiens(IntervalView<FloatType> Gradx, IntervalView<FloatType> Grady) {
final RandomAccess<FloatType> Gradx_r2 = Gradx.randomAccess();
final RandomAccess<FloatType> Grady_r2 = Grady.randomAccess();
//scale, divide the number of integrated bins
for (int i = 0; i < N_Grad; ++i)
{
Gradx_r2.setPosition(i,0);
Grady_r2.setPosition(i,0);
Gradx_r2.get().set((float) (Gradx_r2.get().getRealFloat() / 255.0));
Grady_r2.get().set((float) (Grady_r2.get().getRealFloat() / 255.0));
}
}
private void integrateOverRowAndCol(RandomAccess<FloatType> Grad_dist, IntervalView<FloatType> Gradx, IntervalView<FloatType> Grady) {
final int[] loc = new int[2];
// pGrad2D has 2D CDF
final RandomAccess<FloatType> Gradx_r = Gradx.randomAccess();
// Integrate over the row
for (int i = 0; i < N_Grad; ++i)
{
loc[1] = i;
Gradx_r.setPosition(i,0);
// get the row
for (int j = 0; j < N_Grad; ++j)
{
loc[0] = j;
// Set the position
Grad_dist.setPosition(loc);
// integrate over the row to get 1D vector
Gradx_r.get().set(Gradx_r.get().getRealFloat() + Grad_dist.get().getRealFloat());
}
}
final RandomAccess<FloatType> Grady_r = Grady.randomAccess();
// Integrate over the column
for (int i = 0; i < N_Grad; ++i)
{
loc[1] = i;
Grady_r.setPosition(0,0);
for (int j = 0; j < N_Grad; ++j)
{
loc[0] = j;
Grad_dist.setPosition(loc);
Grady_r.get().set(Grady_r.get().getRealFloat() + Grad_dist.get().getRealFloat());
Grady_r.move(1,0);
}
}
}
private <T extends RealType<T>> void calculateLaplaceFieldAndGradient(Img<T> image, Img<FloatType> LapCDF, Img<FloatType> GradD) {
final RandomAccess<FloatType> Grad_dist = GradD.randomAccess();
final long[] origImgDimensions = new long[2];
image.dimensions(origImgDimensions);
final Img<FloatType> laplaceField = new ArrayImgFactory<FloatType>().create(origImgDimensions, new FloatType());
// Cursor localization
final int[] indexD = new int[2];
final int[] loc_p = new int[2];
final RandomAccess<T> img_cur = image.randomAccess();
final RandomAccess<FloatType> Lap_f = laplaceField.randomAccess();
final RandomAccess<FloatType> Lap_hist = LapCDF.randomAccess();
// Normalization 1/(Number of pixel of the original image)
long n_pixel = 1;
for (int i = 0 ; i < laplaceField.numDimensions() ; i++)
{n_pixel *= laplaceField.dimension(i)-2;}
// unit to sum
final double f = 1.0/(n_pixel);
// Inside the image for Y
final Cursor<FloatType> cur = laplaceField.cursor();
// For each point of the Laplacian field
while (cur.hasNext())
{
cur.next();
// Localize cursors
cur.localize(loc_p);
// Exclude the border
boolean border = false;
for (int i = 0 ; i < image.numDimensions() ; i++)
{
if (loc_p[i] == 0)
{border = true;}
else if (loc_p[i] == image.dimension(i)-1)
{border = true;}
}
if (border == true) {
continue;
}
// get the stencil value;
img_cur.setPosition(loc_p);
float L = -4*img_cur.get().getRealFloat();
// Laplacian
for (int i = 0 ; i < 2 ; i++)
{
img_cur.move(1, i);
final float G_p = img_cur.get().getRealFloat();
img_cur.move(-1,i);
final float G_m = img_cur.get().getRealFloat();
img_cur.move(-1, i);
final float L_m = img_cur.get().getRealFloat();
img_cur.setPosition(loc_p);
L += G_p + L_m;
// Calculate the gradient + convert into bin
indexD[1-i] = (int) (Grad_Offset + G_p - G_m);
}
Lap_f.setPosition(loc_p);
// Set the Laplacian field
Lap_f.get().setReal(L);
// Histogram bin conversion
L += Lap_Offset;
Lap_hist.setPosition((int)(L),0);
Lap_hist.get().setReal(Lap_hist.get().getRealFloat() + f);
Grad_dist.setPosition(indexD);
Grad_dist.get().setReal(Grad_dist.get().getRealFloat() + f);
}
}
private void convertGrad2dToCDF(Img<FloatType> GradD) {
final RandomAccess<FloatType> Grad_dist = GradD.randomAccess();
final int[] loc = new int[GradD.numDimensions()];
// for each row
for (int j = 0; j < GradD.dimension(1); ++j)
{
loc[1] = j;
for (int i = 1; i < GradD.dimension(0) ; ++i)
{
loc[0] = i-1;
Grad_dist.setPosition(loc);
// Precedent float
final float prec = Grad_dist.get().getRealFloat();
// Move to the actual position
Grad_dist.move(1, 0);
// integration up to the current position
Grad_dist.get().set(Grad_dist.get().getRealFloat() + prec);
}
}
//col integration
for (int j = 1; j < GradD.dimension(1); ++j)
{
// Move to the actual position
loc[1] = j-1;
for (int i = 0; i < GradD.dimension(0); ++i)
{
loc[0] = i;
Grad_dist.setPosition(loc);
// Precedent float
final float prec = Grad_dist.get().getRealFloat();
// Move to the actual position
Grad_dist.move(1, 1);
Grad_dist.get().set(Grad_dist.get().getRealFloat() + prec);
}
}
}
/**
* Show information about authors and paper.
*/
private void showMessage()
{
// Create main window with panel to store gui components
final JDialog win = new JDialog((JDialog)null, "Naturalization", true);
final JPanel msg = new JPanel();
msg.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Create message not editable but still focusable for copying
final JTextPane text = new JTextPane();
text.setContentType("text/html");
text.setText("<html>Y. Gong and I. F. Sbalzarini. Image enhancement by gradient distribution specification. In Proc. ACCV, <br>"
+ "12th Asian Conference on Computer Vision, Workshop on Emerging Topics in Image Enhancement and Restoration,<br>"
+ "pages w7–p3, Singapore, November 2014.<br><br>"
+ "Y. Gong and I. F. Sbalzarini, Gradient Distributions Priors for Biomedical Image Processing, 2014<br>http://arxiv.org/abs/1408.3300<br><br>"
+ "Y. Gong and I. F. Sbalzarini. A Natural-Scene Gradient Distribution Prior and its Application in Light-Microscopy Image Processing.<br>"
+ "IEEE Journal of Selected Topics in Signal Processing, Vol.10, No.1, February 2016, pages 99-114<br>"
+ "ISSN: 1932-4553, DOI: 10.1109/JSTSP.2015.2506122<br><br>"
+ "</html>");
text.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
text.setEditable(false);
msg.add(text);
// Add button "Close" for closing window easily
final JButton button = new JButton("Close");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
win.dispose();
}
});
msg.add(button);
// Finally show window with message
win.add(msg);
win.pack();
win.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
win.setVisible(true);
}
#Override
protected boolean showDialog() {
return true;
}
#Override
protected boolean setup(String aArgs) {
setFilePrefix("naturalized_");
return true;
}
}
I want it to compile it again and get a .class file or a whole .jar file of this plugin.
Which sturcuture and datas I need for get a .class data?
What are with the import files, where i can get the ij, java, javax and net files? In which structure must it be.
I am a novice in Java and only know, that the compiled command is javac.
on linux there is a command to do it which is javac
just : javac HelloWorld.java
it might be the same thing on windows but i am not sure (install a virtual linux box if there is no other way)
If something goes wrong google the error
If you want to compile a Java program from command line you should use the javac command and to invoke it just write java and then the name of your program.
Compiling a file you will have the .class file that you are looking for.
Related
I am trying to create a program that when given the location of a chess knight and the destination, all marked in chess notation, to return the number of moves it takes the knight to get from the location the destination. I have tried before using the algorithm to calculate every single possibility on a list, but it is very slow and kind of has problems. Here is my code:
private static int translateChessNotation(String chess) {
int returned = 8 * (Integer.valueOf(String.valueOf(chess.charAt(1)))- 1);
return returned + (convertAlphabet(chess.charAt(0))); // File
}
public static int knight(String start, String finish) {
int knightPosition = translateChessNotation(start), end = translateChessNotation(finish), i = 0;
ArrayList<Integer> currentPossibleKnightPositions = new ArrayList<>();
currentPossibleKnightPositions.add(knightPosition);
for (; i < 8; i++) {
ArrayList<Integer> newList = new ArrayList<>();
for (int position : currentPossibleKnightPositions) {
newList.add(position + 17);
newList.add(position + 15);
newList.add(position + 10);
newList.add(position + 6);
newList.add(position - 6);
newList.add(position - 10);
newList.add(position - 15);
newList.add(position - 17);
}
ArrayList<Integer> removed = new ArrayList<>();
for (int j : newList) {if (j < 1 || j > 64) {removed.add(j);}}
newList.removeAll(removed);
currentPossibleKnightPositions.clear();
currentPossibleKnightPositions.addAll(newList);
for (int n : currentPossibleKnightPositions) {
if (n == end) {return i + 1;}
}
}
return -1;
}
Thanks a lot if you help!
Here's a little Proggy to solve the so-called Knights-Tour problem, visiting all squares on the board starting from a particular location, so you could adapt that to set a particular to-position as your end-condition.
Its just Brute-Force, trying all possible combinations & takes about 50 minutes to find each full Knights-Tour solution.
If that helps, I'd be honoured to receive your vote.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class KnightMove {
#SuppressWarnings("serial")
private static final class KnightMoveSolvedException extends RuntimeException {
private final byte[][] solution;
private KnightMoveSolvedException(final byte[][] solution) {
this.solution = solution;
}
}
private static final int SIZE_X = 8;
private static final int SIZE_Y = 8;
private static final int SIZE_X_Y = SIZE_X * SIZE_Y; // Max 127! (=0x7F)
private static final int [][] KNIGHT_MOVES = new int[8][];
/**/ static {
final AtomicInteger moveIndex = new AtomicInteger();
IntStream.of(2, -2).forEach(deltaX ->
IntStream.of(1, -1).forEach(deltaY -> {
/*
* Mirror the 4 combinations above to get all 8 possible Knight moves...
*/
KNIGHT_MOVES[moveIndex.getAndIncrement()] = new int[] {deltaX, deltaY};
KNIGHT_MOVES[moveIndex.getAndIncrement()] = new int[] {deltaY, deltaX};
}));
}
private static void nextMoveToXY(int moveCount, final int x, final int y, final byte[][] board) {
moveCount++;
board[x][y] = (byte) moveCount;
if (moveCount >= SIZE_X_Y) {
System.out.println("Solved!.....: count=" + moveCount);
for ( final byte[] column : board ) {
for (final byte square : column) {
System.out.print(square + "\t");
}
System.out.println();
}
return; // (Back up & keep looking for next solution)
/*
* If 1 solution is enough, just throw the Exception...
*/
// throw new KnightMoveSolvedException(board);
}
for (final int[] knightMove : KNIGHT_MOVES) {
final int newX = x + knightMove[0]; if (newX < 0 || newX >= SIZE_X) {continue;}
final int newY = y + knightMove[1]; if (newY < 0 || newY >= SIZE_Y) {continue;}
if (board[newX][newY] == 0) {
/*
* Target Square is vacant, so try this move recursively...
*/
nextMoveToXY(moveCount, newX, newY, deepPrimitive2DArrayClone(board));
}
}
}
/**
* {#link Object#clone()} can create a Deep Clone of a 1D array of Primitives
* but will <b>not</b> deliver the desired result with 2D,
* so we have to wrap the rest by hand...
*/
private static byte[][] deepPrimitive2DArrayClone(final byte[][] source) {
final byte[][] clone = new byte[source.length][];
/**/ int cix = 0;
for (final byte[] col : source) {
clone[cix++] = col.clone();
}
return clone;
}
public static void main(final String[] args) throws Exception {
IntStream.range(0, SIZE_X).forEach(x ->
IntStream.range(0, SIZE_Y).forEach(y -> {
try {
System.out.println("Solve starting at X/Y.: " + x +"/" + y);
nextMoveToXY(0, x, y, new byte[SIZE_X][SIZE_Y]);
}
catch (final KnightMoveSolvedException e) {
System.out.println(e.solution);
}
}));
}
}
I got this answer online. Hope this helps to the others who have the same question!
public static int knight(String...pos) {
int[][] ab=Stream.of(pos).map(s->new int[]{"abcdefgh".indexOf(s.charAt(0)),s.charAt(1)-48}).toArray(int[][]::new);
int[] dxy=IntStream.range(0,2).map(i->Math.abs(ab[0][i]-ab[1][i])).sorted().toArray();
if(dxy[0]==0&&dxy[1]==1) return 3;
if(dxy[0]==2&&dxy[1]==2||dxy[0]==1&&dxy[1]==1&&(pos[0]+pos[1]).matches(".*?(a1|h1|a8|h8).*")) return 4;
int delta=dxy[1]-dxy[0];
return delta-2*(int)Math.floor(1.0*(delta-dxy[0])/(dxy[0]>delta?3:4));
}
I've replaced the Chessboard Array in the previous Posting with a long.
(with 64 bits, its just large enough to represent the board)
The new Version is significantly faster.
Depending on starting-coordinates, a solution takes anywhere between 1 Minute & 12 Hours...
(I've put a couple of the faster ones first)
This example is designed to show the basics. There are various Mathematical Methods (see Wikipedia) to optimise it, but they make the Solution more complex.
A couple of Takeaways:
- use Primitives (byte, short, int, long,...) if you can: they are very fast
- avoid Objects like ArrayList when using Brute-Force: they are very slow
- use recursion: it saves & restores State for you. It may cost a little, but it makes life so much easier
- use final whenever you can: it's no faster, but aids understanding
Hope you like it. :-)
I've honed this thing down now. It is massively faster than the original (which was no slouch!), uses the Warnsdorff algorithm & can solve multiple starting positions, running on all available Threads simultaneously.
Most of the work is getting the Data Structures right & Initialisation.
The recursive nextMoveToXY solver Method itself is trivially simple.
The Warnsdorff Version:
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
public class KnightsTourWarnsdorff {
private interface IntIntConsumer {
void accept(int t, int u);
}
private static final int MAX_INSTANT_TO_STRING_LENGTH = "2020-12-31T23:59:59.123456Z".length();
private static final int SIZE_X = 8;
private static final int SIZE_Y = 8;
private static final int SIZE_X_Y = SIZE_X * SIZE_Y;
/**/ static { check_SIZE_X_Y_withinCapacity();}
/**
* Do this in a Method (we don't want to mark the Class with SuppressWarnings)
*/
#SuppressWarnings("unused")
private static void check_SIZE_X_Y_withinCapacity() {
if (SIZE_X_Y > Long.SIZE) {
throw new UnsupportedOperationException("Number of squares on board exceeds capacity of long Solution");
}
}
/**
* Returns the unique offset corresponding to a Move or our position on the Board.
*/
private static int getDeltaXY(final int deltaX, final int deltaY) {
return deltaX + deltaY * SIZE_X; /* Yes, SIZE_X ! */
}
/**
* Returns a long with a single bit set, corresponding to our position on the Board.
*/
private static long getXYmaskBit(final int x, final int y) {
return 1L << (63 - getDeltaXY(x, y));
}
private static void walkBoard(final IntIntConsumer doXY) {
walkBoard(null, doXY, null);
}
private static void walkBoard(final IntConsumer doRowStart, final IntIntConsumer doXY, final Runnable doRowEnd) {
IntStream .range(0, SIZE_Y).forEach(y -> {if (doRowStart != null) {doRowStart.accept( y);}
IntStream.range(0, SIZE_X).forEach(x -> {if (doXY != null) {doXY .accept(x,y);}
}); if (doRowEnd != null) {doRowEnd .run ( );}
});
}
private static String toBinary(final long value) {
return leftPad(Long.SIZE, Long.toBinaryString(value)).replace('0', '_');
}
private static String leftPad (final int paddedLength, final String value) {
final int padCount = Math.max(0, paddedLength - value.length());
final char[] pad = new char[padCount];
Arrays.fill (pad, '0');
return String.valueOf(pad).concat(value);
}
private static String rightPad (final int paddedLength, final String value) {
final int padCount = Math.max(0, paddedLength - value.length());
final char[] pad = new char[padCount];
Arrays.fill (pad, '0');
return value.concat(String.valueOf(pad));
}
private static String header () {
return rightPad (MAX_INSTANT_TO_STRING_LENGTH, Instant.now().toString()) + " " + Thread.currentThread().getName() + " ";
}
/**
* Square on Board not only knows its x/y location, but also its position as an xyMask<br>
* (for checking whether a square is occupied & marking as occupied).<br>
* <br>
* It knows all possible Moves from this Square within the Board<br>
* (thus obviating the need to check whether we're still on the Board).<br>
* <br>
* Each Specific Move contains a reference to the Target Square, which in turn...<br>
* (these 2 measures speed up Navigation massively)
*/
private static final class Square {
private final int x;
private final int y;
/**
* Used to mark the Square as occupied on the Board
*/
private final long xyMask;
/**
* All possible Moves from this Square.<br>
* (initially all null: filled after all Squares have been instantiated)
*/
private final Move[] targetMove;
private Square(final int x, final int y) {
this.x = x;
this. y = y;
this.xyMask = getXYmaskBit(x, y);
this.targetMove = KNIGHT_MOVE_MAP.values().stream().filter(move -> {
final int newX = x + move.deltaX;
final int newY = y + move.deltaY;
return newX >= 0 && newX < SIZE_X
&& newY >= 0 && newY < SIZE_Y;
}).toArray(Move[]::new);
}
}
/**
* Either a Generic or a Specific Move
*/
private static final class Move {
private final int deltaX;
private final int deltaY;
private final int deltaXY;
private final Square target;
/**
* Create a Generic Move
*/
private Move(final int deltaX, final int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
this.deltaXY = getDeltaXY(deltaX, deltaY);
this.target = null;
}
/**
* Create a Move to a specific Target Square
*/
private Move(final Move genericMove, final Square target) {
this.deltaX = genericMove.deltaX;
this.deltaY = genericMove.deltaY;
this.deltaXY = genericMove.deltaXY;
this.target = target;
}
}
#SuppressWarnings("serial")
private static final class KnightMoveSolvedException extends RuntimeException {
private final int[] solution;
private KnightMoveSolvedException(final int moveCount, final int[] solution) {
/*
* Trim the solution array down to the number of moves...
* (for those performing a partial walk)
*/
this.solution = Arrays.stream(solution).limit(moveCount).toArray();
synchronized (KnightMoveSolvedException.class) { // One Thread (= Solution) at a time please!
final int solution0 = this.solution[0];
final Move initialMove = BOARD_MAP.get(solution0);
final int initialX = initialMove.deltaX;
final int initialY = initialMove.deltaY;
System.out.println(header() + "Solution found for....: x/y: " + initialX + "/" + initialY + " \t" + toBinary(0L) + " \tlength=" + this.solution.length + " \t" + solution0);
this.printSolutionDetail();
}
}
private void printSolutionDetail() {
int x = 0;
int y = 0;
long board = 0;
for (int i=0; i < this.solution.length; i++) {
final int positionOrMove = this.solution[i];
final Move move = i == 0 ? BOARD_MAP.get(positionOrMove) : KNIGHT_MOVE_MAP.get(positionOrMove);
/**/ x = i == 0 ? move.deltaX : x + move.deltaX;
/**/ y = i == 0 ? move.deltaY : y + move.deltaY;
board |= getXYmaskBit(x, y);
System.out.println(header() + "Solution walk.........: x/y: " + x + "/" + y + " \t" + toBinary(board) + " \t" + move.deltaX + "\t" + move.deltaY + "\t" + positionOrMove);
}
}
}
private static final Map<Integer, Move> KNIGHT_MOVE_MAP;
/**/ static {
final Map<Integer, Move> Knight_Move_Map = new TreeMap<>();
IntStream.of(2, -2).forEach(deltaX ->
IntStream.of(1, -1).forEach(deltaY -> {
/*
* Mirror the 4 combinations above to get all 8 possible Knight moves...
*/
{final Move move = new Move(deltaX, deltaY); Knight_Move_Map.put(move.deltaXY, move);}
{final Move move = new Move(deltaY, deltaX); Knight_Move_Map.put(move.deltaXY, move);}
}));
KNIGHT_MOVE_MAP = Collections.unmodifiableMap(Knight_Move_Map);
}
private static final Map<Integer, Move> BOARD_MAP;
/**/ static {
final Map<Integer, Move> Board_Map = new TreeMap<>();
walkBoard((x,y) -> {
final Move move = new Move(x, y);
Board_Map.put(move.deltaXY, move);
});
BOARD_MAP = Collections.unmodifiableMap(Board_Map);
}
private static final Square[][] BOARD = new Square[SIZE_X] [SIZE_Y];
/**/ static {
/*
* Fill the Board with Squares...
*/
walkBoard( (x,y) -> BOARD[x][y] = new Square(x, y));
/**/ System.out.println("Onward Target Count:");
walkBoard( ( y) -> { System.out.print ( y + " : ");},
/**/ (x,y) -> {final Square square = BOARD[x][y]; System.out.print (square.targetMove.length + " ");},
/**/ ( ) -> { System.out.println() ;} );
/*
* So far the Target Moves array is filled with nulls. We MUST fill it...
*/
Arrays.stream(BOARD).flatMap(Arrays::stream).forEach(square -> {
final Move[] targetsSortedByOnwardPointCount = Arrays
.stream(square.targetMove)
.sorted((moveA, moveB) -> {
/*
* We use the Warnsdorff algorithm to sort it by the number of Onward Targets...
*/
final Square targetA = BOARD[square.x + moveA.deltaX] [square.y + moveA.deltaY];
final Square targetB = BOARD[square.x + moveB.deltaX] [square.y + moveB.deltaY];
return Integer.compare(
targetA.targetMove.length, // number of Onward Targets
targetB.targetMove.length); // number of Onward Targets
})
.map(move -> new Move(move, BOARD[square.x + move.deltaX] [square.y + move.deltaY]))
.toArray(Move[]::new);
/*
* Original & sorted arrays should be the same length if we got it right,
* so take max. length as a precaution to force an IndexOutOfBoundsException if we didn't...
*/
final int copyLength = Math.max(square.targetMove.length, targetsSortedByOnwardPointCount.length);
/*
* Overwrite the original Moves with the sorted version...
*/
System.arraycopy(targetsSortedByOnwardPointCount, 0, square.targetMove, 0, copyLength);
});
}
private final int[] SOLUTION = new int[SIZE_X_Y];
private void solve(final int initialX, final int initialY) {
final long initialBoard = getXYmaskBit(initialX, initialY);
System.out.println(header() + "Solve starting at.....: x/y: " + initialX +"/" + initialY + "\t" + toBinary(initialBoard));
try {
SOLUTION [0] = getDeltaXY(initialX, initialY); // First Entry contains Starting-Point
nextMoveToXY(0, BOARD[initialX][initialY], initialBoard);
}
catch (final KnightMoveSolvedException justIgnore_WereDone) {}
}
private void nextMoveToXY(int moveCount, final Square square, final long board) {
moveCount++;
if (moveCount >= SIZE_X_Y) {
final KnightMoveSolvedException solution = new KnightMoveSolvedException(moveCount, SOLUTION);
// return; // (Back up & keep looking for next solution)
/*
* If 1 solution is enough, just throw the Exception...
*/
throw solution;
}
for (final Move move : square.targetMove) {
/*
* Is Target Square vacant? (i.e. Mask Bit not set)...
*/
if ((board & move.target.xyMask) == 0) {
/*
* Yes: try next move recursively with new Position & Board...
*/
SOLUTION [moveCount] = move.deltaXY;
nextMoveToXY(moveCount, move.target, board | move.target.xyMask /* Set Mask Bit on new Board */);
}
}
}
public static void main(final String[] args) throws Exception {
final ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
/*
* We can handle rectangular boards, but for square boards the following holds:
* we only need to solve for 1/8 of the board (a triangle)...
* (the remaining 7/8 are either Mirrors or Rotations of the 1/8)
*/
IntStream .range(0, SIZE_X / 2).forEach(x -> {
IntStream.range(0, x + 1 ).forEach(y -> {
pool.submit(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (final InterruptedException e) {}
/*
* (Sleep very briefly, so our Thread won't start before the Output below has finished)
*/
new KnightsTourWarnsdorff().solve(x, y);
});
System.out.print("x=" + x + " y=" + y + "\t");
});
System.out.println();
});
pool.shutdown();
}
}
Original Version:
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class KnightsTour {
#SuppressWarnings("serial")
private static final class KnightMoveSolvedException extends RuntimeException {
private final int[][] solution;
private KnightMoveSolvedException(final int[][] solution) {
this.solution = deepPrimitive2DArrayClone (solution);
}
}
private static final int SIZE_X = 8;
private static final int SIZE_Y = 8;
private static final int SIZE_X_Y = SIZE_X * SIZE_Y;
private static final int[][] SOLUTION = new int[SIZE_X_Y][];
private static final int INDEX_X = 0;
private static final int INDEX_Y = 1;
private static final int KNIGHT_MOVES_LENGTH = 8;
private static final int [][] KNIGHT_MOVES = new int[KNIGHT_MOVES_LENGTH][];
/**/ static {
checkLongSolutionCapacity();
final AtomicInteger moveIndex = new AtomicInteger();
IntStream.of(2, -2).forEach(deltaX ->
IntStream.of(1, -1).forEach(deltaY -> {
/*
* Mirror the 4 combinations above to get all 8 possible Knight moves...
*/
KNIGHT_MOVES[moveIndex.getAndIncrement()] = new int[] {deltaX, deltaY};
KNIGHT_MOVES[moveIndex.getAndIncrement()] = new int[] {deltaY, deltaX};
}));
}
#SuppressWarnings("unused")
private static void checkLongSolutionCapacity() {
if (SIZE_X_Y > Long.SIZE) {
throw new UnsupportedOperationException("Number of squares on board exceeds capacity of long Solution");
}
}
private static long getXYmaskBit(final int x, final int y) {
return Long.MIN_VALUE >>> (x + y * SIZE_X /* Yes, SIZE-X ! */);
}
public static void solve(final int initialX, final int initialY) {
final long initialBoard = getXYmaskBit(initialX, initialY);
System.out.println("Solve starting at X/Y.: " + initialX +"/" + initialY + "\t" + toBinary(initialBoard));
try {
SOLUTION [0] = new int[] {initialX, initialY}; // First Entry contains Starting-Point
nextMoveToXY(0, initialX, initialY, initialBoard);
}
catch (final KnightMoveSolvedException e) {
System.out.println("One possible solution.: " + e.solution);
}
}
private static void nextMoveToXY(int moveCount, final int x, final int y, final long board) {
moveCount++;
if (moveCount >= SIZE_X_Y) {
System.out.println("Solved!...............: count=" + moveCount);
/*
* Print the answer or remember it somewhere...
*/
final int initialX = SOLUTION[0][INDEX_X];
final int initialY = SOLUTION[0][INDEX_Y];
for(final int[] move : SOLUTION) {
final int solutionX = move[INDEX_X];
final int solutionY = move[INDEX_Y];
System.out.println("Move (starting at X/Y): " + initialX +"/" + initialY + "\t" + toBinary(board) + "\t" + solutionX + "\t" + solutionY);
}
// return; // (Back up & keep looking for next solution)
/*
* If 1 solution is enough, just throw the Exception...
*/
throw new KnightMoveSolvedException(SOLUTION);
}
for(final int[] move : KNIGHT_MOVES) {
final int deltaX = move[INDEX_X]; final int newX = x + deltaX; if (newX < 0 || newX >= SIZE_X) {continue;}
final int deltaY = move[INDEX_Y]; final int newY = y + deltaY; if (newY < 0 || newY >= SIZE_Y) {continue;}
/*
* ok: Checks above mean we're on the board, so lets find the new Position Mask...
*/
final long newXYmaskBit = getXYmaskBit(newX, newY);
/*
* Is Target Square vacant (= Mask Bit not set)?...
*/
if ((board & newXYmaskBit) == 0) {
/*
* Yes: try next move recursively with new Position & Board...
*/
SOLUTION [moveCount] = move;
nextMoveToXY(moveCount, newX, newY, board | newXYmaskBit /* Set Mask Bit on new Board */);
}
}
}
public static String toHex (final int value) {
return leftPad(Integer.BYTES * 2, Integer.toHexString (value));
}
public static String toHex (final long value) {
return leftPad(Long .BYTES * 2, Long .toHexString (value));
}
public static String toBinary(final int value) {
return leftPad(Integer.SIZE, Integer.toBinaryString(value));
}
public static String toBinary(final long value) {
return leftPad(Long .SIZE, Long .toBinaryString(value));
}
private static String leftPad (final int paddedLength, final String binaryOrHex) {
final char[] lead = new char[paddedLength - binaryOrHex.length()];
Arrays.fill (lead, '0');
return String.valueOf(lead).concat(binaryOrHex).replace('0', '_');
}
/**
* {#link Object#clone()} can create a Deep Clone of a 1D array of Primitives
* but with 2D will only provide a Shallow Copy (meaning if the content of source
* changes, the content of clone will change!!) so we have to wrap 2D by hand...
*/
private static int[][] deepPrimitive2DArrayClone(final int[][] source) {
final int[][] clone = new int[source.length][];
/**/ int cix = 0;
for (final int[] col : source) {
clone[cix++] = col.clone(); // (ok: 1D, so Deep Clone)
}
return clone;
}
public static void main(final String[] args) throws Exception {
solve(0, 1); // Fast!: 2 Minutes
solve(0, 3); // Fast!: 1 Minute
IntStream.range(0, SIZE_X).forEach(x ->
IntStream.range(0, SIZE_Y).forEach(y -> {
solve(x, y);
}));
}
}
For my project I was wondering whether there is a way I can do this assignment without using serialization. Here are the guidelines to the project and the code I already have together:
The Canadian Forest Service wants to do a simple simulation of the growth and pruning of forests. Each forest has a name and exactly 10 trees. The trees are planted when they are 1' to 5' tall, and each tree has a individual growth rate of 50%-100% per year. For the simulation new trees are constructed randomly within these bounds. A forest is reaped (by lumberjacks) on demand - all trees above a specifed height are cut down and replaced with new trees.
The user interface to the simulation must allow the user to:
Display the current forest (with tree heights to 2 decimal places)
Discard the current forest and create a new forest
Simulate a year's growth in the current forest
Reap the current forest of trees over a user specified height, replacing the reaped trees with random new trees.
Save the information about the current forest to file (named after the forest)
Discard the current forest and load the information about a forest from a file.
Class1
import java.io.*;
import java.util.*;
public class Forest{
//constants
private static final int MAX_NUM_TREES = 10;
//variables
int index;
private String name;
private Tree[] arrayOfTrees;
public Forest(String forestName){
//Constructor class that takes a name and creates an array of trees().
index = 0;
name = forestName;
arrayOfTrees = new Tree[MAX_NUM_TREES];
for(index = 0; index < arrayOfTrees.length; index++){
arrayOfTrees[index] = new Tree();
}
}
public void display(){
// displays the array of trees and the index
index = 0;
if(name != null){
System.out.println(name);
for(index = 0; index < arrayOfTrees.length; index ++){
System.out.printf("%2d : %s\n", (index + 1), arrayOfTrees[index]);
}
}else{
System.out.println("No forest.");
}
}
public void yearGrowth(){
//grows each tree in the array
index = 0;
for(index = 0; index < arrayOfTrees.length ; index ++){
arrayOfTrees[index].grow();
}
}
public void reap(int reapHeight){
//reaps the trees and prints out the old and new information
index = 0;
for(index = 0; index < arrayOfTrees.length; index++){
if(arrayOfTrees[index].getHeight() >= reapHeight){
System.out.println("Cut " + (index+1) + " : " + arrayOfTrees[index] );
arrayOfTrees[index] = new Tree();
System.out.println("New " + (index+1) + " : " + arrayOfTrees[index] );
}
}
}
public static void saveForest(Forest forest) throws IOException {
//saves the forest
String name = forest.getName();
ObjectOutputStream toStream;
toStream = new ObjectOutputStream(new FileOutputStream(name));
toStream.writeObject(forest);
toStream.close();
}
public static Forest loadForest(String fileName) throws IOException {
//loads the forest
ObjectInputStream fromStream = null;
Forest local;
fromStream = new ObjectInputStream(new FileInputStream(fileName));
try {
local = (Forest)fromStream.readObject();
}catch (ClassNotFoundException e) {
System.out.println(e.getMessage());
return(null);
}finally{
try {
if (fromStream != null) {
fromStream.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
return(null);
}
}
return(local);
}
public String getName(){
return (name);
}
}
Class2
import java.util.Random;
import java.util.*;
import java.io.*;
public class Tree{
//creates the variables as the
private double height;
private double growthRate;
private static Random rand = new Random();
final double MIN_HEIGHT = 1;
final double MIN_GROWTH_RATE = 0.5;
final double MAX_HEIGHT = 5;
final double MAX_GROWTH_RATE = 1.0;
public Tree() {
//creates tree with a height and a growth rate
Random rand = new Random();
height = (MIN_HEIGHT + ((Math.random() * (MAX_HEIGHT - MIN_HEIGHT))));
growthRate = (MIN_GROWTH_RATE + (Math.random() * (MAX_GROWTH_RATE - MIN_GROWTH_RATE)));
}
public double grow(){
//tree grows and returns height
height = height * (1 + growthRate);
return height;
}
public double getHeight(){
return (height);
}
public double getGrowthRate(){
return (growthRate);
}
public String toString(){
//toString formats the output with height and growthrate
return (String.format("%7.2f (%2d%% pa)", height, ((int)(growthRate * 100))));
}
}
If by serialization you understand standard java serialization with ObjectXXXStream, then yes, you can avoid it.
If you mean serialization in more broad way, then no. Files cant directly store java objects you have to convert them to bytes (which is serialization by definition).
PS: If you actually ask "How?" you should include it in your question.
I have some code that takes 3 different PDF byte arrays and merges them. This code works great. The issue (some people) are having is that each PDF is considered to be a full page (if printed) even if there is only say 4 inches of content on it, thus leaving 7 inches of white space vertically. Then the middle document gets put in and may or may not have vertical white space at the end of it. Then the footer gets put on its own page as well.
Here is the code:
byte[] Bytes = rv.LocalReport.Render("PDF", null, out MimeType, out Encoding, out Extension, out StreamIDs, out Warnings);
List<byte[]> MergeSets = // This is filled prior to this code
// Append any other pages to this primary letter
if (MergeSets.Count > 0) {
MemoryStream ms = new MemoryStream();
Document document = new Document();
PdfCopy copy = new PdfCopy(document, ms);
document.Open();
PdfImportedPage page;
PdfReader reader = new PdfReader(Bytes); // read the generated primary Letter
int pages = reader.NumberOfPages;
for (int i = 0; i < pages; ) {
page = copy.GetImportedPage(reader, ++i);
copy.AddPage(page);
} // foreach of the pages in the Cover Letter
// Now append the merge sets
foreach (byte[] ba in MergeSets) {
reader = new PdfReader(ba);
pages = reader.NumberOfPages;
for (int i = 0; i < pages; ) {
page = copy.GetImportedPage(reader, ++i);
copy.AddPage(page);
} // foreach of the pages in the current merge set
} // foreach of the sets of data
document.Close();
ServerSaved = SaveGeneratedLetter(ms.GetBuffer(), DateTime.Now.Year, hl.LetterName, SaveName);
} // if there is anything to merge
Is there a way when I am merging each page to clip/remove/erase the vertical white space at the end of each pdf so it appears as one seamless document?
UPDATE:
Here are some sample .pdf files I am trying to merge.
header, body, footer
UPDATE 2: USING THE ANSWER:
I have converted #mkl's code to C# and here it is.
The tool class:
public class PdfVeryDenseMergeTool {
private Rectangle PageSize;
private float TopMargin;
private float BottomMargin;
private float Gap;
private Document Document = null;
private PdfWriter Writer = null;
private float YPosition = 0;
public PdfVeryDenseMergeTool(Rectangle size, float top, float bottom, float gap) {
this.PageSize = size;
this.TopMargin = top;
this.BottomMargin = bottom;
this.Gap = gap;
} // PdfVeryDenseMergeTool
public void Merge(MemoryStream outputStream, List<PdfReader> inputs) {
try {
this.OpenDocument(outputStream);
foreach (PdfReader reader in inputs) {
this.Merge(reader);
} // foreach of the PDF files to merge
} finally {
this.CloseDocument();
} // try-catch-finally
} // Merge
public void OpenDocument(MemoryStream outputStream) {
this.Document = new Document(PageSize, 36, 36, this.TopMargin, this.BottomMargin);
this.Writer = PdfWriter.GetInstance(Document, outputStream);
this.Document.Open();
this.NewPage();
} // OpenDocument
public void CloseDocument() {
try {
this.Document.Close();
} finally {
this.Document = null;
this.Writer = null;
this.YPosition = 0;
} // try-finally
} // CloseDocument
public void NewPage() {
this.Document.NewPage();
this.YPosition = PageSize.GetTop(this.TopMargin);
} // Merge
public void Merge(PdfReader reader) {
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int pageIndex = 1; pageIndex <= reader.NumberOfPages; pageIndex++) {
this.Merge(reader, parser, pageIndex);
} // foreach of the pages of the current PDF
} // Merge
public void Merge(PdfReader reader, PdfReaderContentParser parser, int pageIndex) {
PdfImportedPage importedPage = Writer.GetImportedPage(reader, pageIndex);
PdfContentByte directContent = Writer.DirectContent;
PageVerticalAnalyzer finder = parser.ProcessContent(pageIndex, new PageVerticalAnalyzer());
if (finder.VerticalFlips.Count < 2)
return;
Rectangle pageSizeToImport = reader.GetPageSize(pageIndex);
int startFlip = finder.VerticalFlips.Count - 1;
bool first = true;
while (startFlip > 0) {
if (!first)
this.NewPage();
float freeSpace = this.YPosition - PageSize.GetBottom(BottomMargin);
int endFlip = startFlip + 1;
while ((endFlip > 1) && (finder.VerticalFlips[startFlip] - finder.VerticalFlips[endFlip - 2] < freeSpace))
endFlip -= 2;
if (endFlip < startFlip) {
float height = finder.VerticalFlips[startFlip] - finder.VerticalFlips[endFlip];
directContent.SaveState();
directContent.Rectangle(0, this.YPosition - height, pageSizeToImport.Width, height);
directContent.Clip();
directContent.NewPath();
this.Writer.DirectContent.AddTemplate(importedPage, 0, this.YPosition - (finder.VerticalFlips[startFlip] - pageSizeToImport.Bottom));
directContent.RestoreState();
this.YPosition -= height + this.Gap;
startFlip = endFlip - 1;
} else if (!first) {
throw new ArgumentException(string.Format("Page {0} content too large", pageIndex));
} // if
first = false;
} // while
} // Merge
} // PdfVeryDenseMergeTool
The RenderListener class:
UPDATE 3: FIXED 1 LINE OF CODE AND IT WORKS: See comment in code
public class PageVerticalAnalyzer : IRenderListener {
public PageVerticalAnalyzer() { }
public List<float> VerticalFlips = new List<float>();
public void AddVerticalUseSection(float from, float to) {
if (to < from) {
float temp = to;
to = from;
from = temp;
}
int i = 0;
int j = 0;
for (i = 0; i < VerticalFlips.Count; i++) {
float flip = VerticalFlips[i];
if (flip < from)
continue;
for (j = i; j < VerticalFlips.Count; j++) {
flip = VerticalFlips[j];
if (flip < to)
continue;
break;
}
break;
} // foreach of the vertical flips
bool fromOutsideInterval = i % 2 == 0;
bool toOutsideInterval = j % 2 == 0;
while (j-- > i)
VerticalFlips.RemoveAt(j); // This was the problem line with just .Remove(j)
if (toOutsideInterval)
VerticalFlips.Insert(i, to);
if (fromOutsideInterval)
VerticalFlips.Insert(i, from);
} // AddVerticalUseSection
public void BeginTextBlock() { /* Do nothing */ }
public void EndTextBlock() { /* Do nothing */ }
public void RenderImage(ImageRenderInfo renderInfo) {
Matrix ctm = renderInfo.GetImageCTM();
List<float> YCoords = new List<float>(4) { 0, 0, 0, 0 };
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
Vector corner = new Vector(x, y, 1).Cross(ctm);
YCoords[2 * x + y] = corner[Vector.I2];
}
}
YCoords.Sort();
AddVerticalUseSection(YCoords[0], YCoords[3]);
} // RenderImage
public void RenderText(TextRenderInfo renderInfo) {
LineSegment ascentLine = renderInfo.GetAscentLine();
LineSegment descentLine = renderInfo.GetDescentLine();
List<float> YCoords = new List<float>(4) {
ascentLine.GetStartPoint()[Vector.I2],
ascentLine.GetEndPoint()[Vector.I2],
descentLine.GetStartPoint()[Vector.I2],
descentLine.GetEndPoint()[Vector.I2],
};
YCoords.Sort();
AddVerticalUseSection(YCoords[0], YCoords[3]);
} // RenderText
} // PageVericalAnalyzer
Code to gather files and run the tool:
public void TestMergeDocuments() {
PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(iTextSharp.text.PageSize.A4, 18, 18, 10);
List<byte[]> Files = new List<byte[]>();
// Code to load each of the 3 files I need into this byte array list
using (MemoryStream ms = new MemoryStream()) {
List<PdfReader> files = new List<PdfReader>();
foreach (byte[] ba in Files) {
files.Add(new PdfReader(ba));
} // foreach of the sets of data
tool.Merge(ms, files);
// Save the file using: ms.GetBuffer()
} // using the memory stream
} // TestMergeDocuments
The following sample tool has been implemented along the ideas of the tool PdfDenseMergeTool from this answer which the OP has commented to be SO close to what [he] NEEDs. Just like PdfDenseMergeTool this tool here is implemented in Java/iText which I'm more at home with than C#/iTextSharp. As the OP has already translated PdfDenseMergeTool to C#/iTextSharp, translating this tool here also should not be too great a problem.
PdfVeryDenseMergeTool
This tool similarly to PdfDenseMergeTool takes the page contents of pages from a number of PdfReader instances and tries to merge them densely, i.e. putting contents of multiple source pages onto a single target page if there is enough free space to do so. In contrast to that earlier tool, this tool even splits source page contents to allow for an even denser merge.
Just like that other tool the PdfVeryDenseMergeTool does not take vector graphics into account because the iText(Sharp) parsing API does only forward text and bitmap images
The PdfVeryDenseMergeTool splits source pages which do not completely fit onto a target page at a horizontal line which is not intersected by the bounding boxes of text glyphs or bitmap graphics.
The tool class:
public class PdfVeryDenseMergeTool
{
public PdfVeryDenseMergeTool(Rectangle size, float top, float bottom, float gap)
{
this.pageSize = size;
this.topMargin = top;
this.bottomMargin = bottom;
this.gap = gap;
}
public void merge(OutputStream outputStream, Iterable<PdfReader> inputs) throws DocumentException, IOException
{
try
{
openDocument(outputStream);
for (PdfReader reader: inputs)
{
merge(reader);
}
}
finally
{
closeDocument();
}
}
void openDocument(OutputStream outputStream) throws DocumentException
{
final Document document = new Document(pageSize, 36, 36, topMargin, bottomMargin);
final PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
this.document = document;
this.writer = writer;
newPage();
}
void closeDocument()
{
try
{
document.close();
}
finally
{
this.document = null;
this.writer = null;
this.yPosition = 0;
}
}
void newPage()
{
document.newPage();
yPosition = pageSize.getTop(topMargin);
}
void merge(PdfReader reader) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int page = 1; page <= reader.getNumberOfPages(); page++)
{
merge(reader, parser, page);
}
}
void merge(PdfReader reader, PdfReaderContentParser parser, int page) throws IOException
{
PdfImportedPage importedPage = writer.getImportedPage(reader, page);
PdfContentByte directContent = writer.getDirectContent();
PageVerticalAnalyzer finder = parser.processContent(page, new PageVerticalAnalyzer());
if (finder.verticalFlips.size() < 2)
return;
Rectangle pageSizeToImport = reader.getPageSize(page);
int startFlip = finder.verticalFlips.size() - 1;
boolean first = true;
while (startFlip > 0)
{
if (!first)
newPage();
float freeSpace = yPosition - pageSize.getBottom(bottomMargin);
int endFlip = startFlip + 1;
while ((endFlip > 1) && (finder.verticalFlips.get(startFlip) - finder.verticalFlips.get(endFlip - 2) < freeSpace))
endFlip -=2;
if (endFlip < startFlip)
{
float height = finder.verticalFlips.get(startFlip) - finder.verticalFlips.get(endFlip);
directContent.saveState();
directContent.rectangle(0, yPosition - height, pageSizeToImport.getWidth(), height);
directContent.clip();
directContent.newPath();
writer.getDirectContent().addTemplate(importedPage, 0, yPosition - (finder.verticalFlips.get(startFlip) - pageSizeToImport.getBottom()));
directContent.restoreState();
yPosition -= height + gap;
startFlip = endFlip - 1;
}
else if (!first)
throw new IllegalArgumentException(String.format("Page %s content sections too large.", page));
first = false;
}
}
Document document = null;
PdfWriter writer = null;
float yPosition = 0;
final Rectangle pageSize;
final float topMargin;
final float bottomMargin;
final float gap;
}
(PdfVeryDenseMergeTool.java)
This tool makes use of a custom RenderListener for use with the iText parser API:
public class PageVerticalAnalyzer implements RenderListener
{
#Override
public void beginTextBlock() { }
#Override
public void endTextBlock() { }
/*
* #see RenderListener#renderText(TextRenderInfo)
*/
#Override
public void renderText(TextRenderInfo renderInfo)
{
LineSegment ascentLine = renderInfo.getAscentLine();
LineSegment descentLine = renderInfo.getDescentLine();
float[] yCoords = new float[]{
ascentLine.getStartPoint().get(Vector.I2),
ascentLine.getEndPoint().get(Vector.I2),
descentLine.getStartPoint().get(Vector.I2),
descentLine.getEndPoint().get(Vector.I2)
};
Arrays.sort(yCoords);
addVerticalUseSection(yCoords[0], yCoords[3]);
}
/*
* #see RenderListener#renderImage(ImageRenderInfo)
*/
#Override
public void renderImage(ImageRenderInfo renderInfo)
{
Matrix ctm = renderInfo.getImageCTM();
float[] yCoords = new float[4];
for (int x=0; x < 2; x++)
for (int y=0; y < 2; y++)
{
Vector corner = new Vector(x, y, 1).cross(ctm);
yCoords[2*x+y] = corner.get(Vector.I2);
}
Arrays.sort(yCoords);
addVerticalUseSection(yCoords[0], yCoords[3]);
}
/**
* This method marks the given interval as used.
*/
void addVerticalUseSection(float from, float to)
{
if (to < from)
{
float temp = to;
to = from;
from = temp;
}
int i=0, j=0;
for (; i<verticalFlips.size(); i++)
{
float flip = verticalFlips.get(i);
if (flip < from)
continue;
for (j=i; j<verticalFlips.size(); j++)
{
flip = verticalFlips.get(j);
if (flip < to)
continue;
break;
}
break;
}
boolean fromOutsideInterval = i%2==0;
boolean toOutsideInterval = j%2==0;
while (j-- > i)
verticalFlips.remove(j);
if (toOutsideInterval)
verticalFlips.add(i, to);
if (fromOutsideInterval)
verticalFlips.add(i, from);
}
final List<Float> verticalFlips = new ArrayList<Float>();
}
(PageVerticalAnalyzer.java)
It is used like this:
PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(PageSize.A4, 18, 18, 5);
tool.merge(output, inputs);
(VeryDenseMerging.java)
Applied to the OP's sample documents
Header.pdf
Body.pdf
Footer.pdf
it generates
If one defines the target document page size to be A5 landscape:
PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(new RectangleReadOnly(595,421), 18, 18, 5);
tool.merge(output, inputs);
(VeryDenseMerging.java)
it generates this:
Beware! This is only a proof of concept and it does not consider all possibilities. E.g. the case of source or target pages with a non-trivial Rotate value is not properly handled. Thus, it is not ready for production use yet.
Improvement in current (5.5.6 SNAPSHOT) iText version
The current iText development version towards 5.5.6 enhances the parser functionality to also signal vector graphics. Thus, I extended the PageVerticalAnalyzer to make use of this:
public class PageVerticalAnalyzer implements ExtRenderListener
{
#Override
public void beginTextBlock() { }
#Override
public void endTextBlock() { }
#Override
public void clipPath(int rule) { }
...
static class SubPathSection
{
public SubPathSection(float x, float y, Matrix m)
{
float effectiveY = getTransformedY(x, y, m);
pathFromY = effectiveY;
pathToY = effectiveY;
}
void extendTo(float x, float y, Matrix m)
{
float effectiveY = getTransformedY(x, y, m);
if (effectiveY < pathFromY)
pathFromY = effectiveY;
else if (effectiveY > pathToY)
pathToY = effectiveY;
}
float getTransformedY(float x, float y, Matrix m)
{
return new Vector(x, y, 1).cross(m).get(Vector.I2);
}
float getFromY()
{
return pathFromY;
}
float getToY()
{
return pathToY;
}
private float pathFromY;
private float pathToY;
}
/*
* Beware: The implementation is not correct as it includes the control points of curves
* which may be far outside the actual curve.
*
* #see ExtRenderListener#modifyPath(PathConstructionRenderInfo)
*/
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
Matrix ctm = renderInfo.getCtm();
List<Float> segmentData = renderInfo.getSegmentData();
switch (renderInfo.getOperation())
{
case PathConstructionRenderInfo.MOVETO:
subPath = null;
case PathConstructionRenderInfo.LINETO:
case PathConstructionRenderInfo.CURVE_123:
case PathConstructionRenderInfo.CURVE_13:
case PathConstructionRenderInfo.CURVE_23:
for (int i = 0; i < segmentData.size()-1; i+=2)
{
if (subPath == null)
{
subPath = new SubPathSection(segmentData.get(i), segmentData.get(i+1), ctm);
path.add(subPath);
}
else
subPath.extendTo(segmentData.get(i), segmentData.get(i+1), ctm);
}
break;
case PathConstructionRenderInfo.RECT:
float x = segmentData.get(0);
float y = segmentData.get(1);
float w = segmentData.get(2);
float h = segmentData.get(3);
SubPathSection section = new SubPathSection(x, y, ctm);
section.extendTo(x+w, y, ctm);
section.extendTo(x, y+h, ctm);
section.extendTo(x+w, y+h, ctm);
path.add(section);
case PathConstructionRenderInfo.CLOSE:
subPath = null;
break;
default:
}
}
/*
* #see ExtRenderListener#renderPath(PathPaintingRenderInfo)
*/
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
for (SubPathSection section : path)
addVerticalUseSection(section.getFromY(), section.getToY());
}
path.clear();
subPath = null;
return null;
}
List<SubPathSection> path = new ArrayList<SubPathSection>();
SubPathSection subPath = null;
...
}
(PageVerticalAnalyzer.java)
A simple test (VeryDenseMerging.java method testMergeOnlyGraphics) merges these files
into this:
But once again beware: this is a mere proof of concept. Especially modifyPath() needs to be improved, the implementation is not correct as it includes the control points of curves which may be far outside the actual curve.
I need to justify some text (RTL), which is a string (S1) from the server. But a TextView can't justify text, so I have to use a WebView, now I have to create a HTML file in which
will display S1. And then I store the address of that html file in the database and then I display that html file. I've seen this question asked before on SO and many have recommended to use a 3rd party library, I've tried all of those approaches to no avail (they work in 90% of scenarios but are no fully reliable).
I feel that this approach seems convoluted, I was wondering if there is a better approach?
I use the following code that answer with very people that need this subject and i create formula that support in every display.
public class TextJustify {
final static String SYSTEM_NEWLINE = "\n";
final static float COMPLEXITY = 5.12f; // Reducing this will increase
// efficiency but will decrease
// effectiveness
final static Paint p = new Paint();
/* #author Mathew Kurian */
public static void run(final TextView tv, float origWidth, int paddingLeft, int paddingRight, int marginLeft, int marginRight) {
origWidth-= paddingRight+marginRight+paddingLeft+marginLeft;
String s = tv.getText().toString();
p.setTypeface(tv.getTypeface());
String[] splits = s.split(SYSTEM_NEWLINE);
float width = origWidth - 5;
for (int x = 0; x < splits.length; x++)
if (p.measureText(splits[x]) > width) {
splits[x] = wrap(splits[x], width, p);
String[] microSplits = splits[x].split(SYSTEM_NEWLINE);
for (int y = 0; y < microSplits.length - 1; y++)
microSplits[y] = justify(removeLast(microSplits[y], " "),
width, p);
StringBuilder smb_internal = new StringBuilder();
for (int z = 0; z < microSplits.length; z++)
smb_internal.append(microSplits[z]
+ ((z + 1 < microSplits.length) ? SYSTEM_NEWLINE
: ""));
splits[x] = smb_internal.toString();
}
final StringBuilder smb = new StringBuilder();
for (String cleaned : splits)
smb.append(cleaned + SYSTEM_NEWLINE);
tv.setGravity(Gravity.RIGHT);
tv.setText(smb);
}
private static String wrap(String s, float width, Paint p) {
String[] str = s.split("\\s"); // regex
StringBuilder smb = new StringBuilder(); // save memory
smb.append(SYSTEM_NEWLINE);
for (int x = 0; x < str.length; x++) {
float length = p.measureText(str[x]);
String[] pieces = smb.toString().split(SYSTEM_NEWLINE);
try {
if (p.measureText(pieces[pieces.length - 1]) + length > width)
smb.append(SYSTEM_NEWLINE);
} catch (Exception e) {
}
smb.append(str[x] + " ");
}
return smb.toString().replaceFirst(SYSTEM_NEWLINE, "");
}
private static String removeLast(String s, String g) {
if (s.contains(g)) {
int index = s.lastIndexOf(g);
int indexEnd = index + g.length();
if (index == 0)
return s.substring(1);
else if (index == s.length() - 1)
return s.substring(0, index);
else
return s.substring(0, index) + s.substring(indexEnd);
}
return s;
}
private static String justifyOperation(String s, float width, Paint p) {
float holder = (float) (COMPLEXITY * Math.random());
while (s.contains(Float.toString(holder)))
holder = (float) (COMPLEXITY * Math.random());
String holder_string = Float.toString(holder);
float lessThan = width;
int timeOut = 100;
int current = 0;
while (p.measureText(s) < lessThan && current < timeOut) {
s = s.replaceFirst(" ([^" + holder_string + "])", " "
+ holder_string + "$1");
lessThan = p.measureText(holder_string) + lessThan
- p.measureText(" ");
current++;
}
String cleaned = s.replaceAll(holder_string, " ");
return cleaned;
}
private static String justify(String s, float width, Paint p) {
while (p.measureText(s) < width) {
s = justifyOperation(s, width, p);
}
return s;
}
}
and for calling this you mus use following code, I tested for Persian language and in every display and device worked fine.
public static final int FinallwidthDp = 320 ;
public static final int widthJustify = 223 ;
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int widthPixels = metrics.widthPixels;
float scaleFactor = metrics.density;
float widthDp = widthPixels / scaleFactor;
TextView tv = (TextView) findViewById(R.id.textView1);
ViewGroup.MarginLayoutParams lp1 = (ViewGroup.MarginLayoutParams) tv.getLayoutParams();
tv.setText(text);
TextJustify.run(tv,widthDp / FinallwidthDp * widthJustify , tv.getPaddingLeft(),tv.getPaddingRight() , lp1.leftMargin, lp1.rightMargin);
this algorithm tested on various device and worked fine in normal activity (not dialog) and wrap-content width for TextView, and worked with every padding and margin.if not good for you, you can change widthJustify until look good to you, I hope this useful.
for newly update see This
LIBRARY: https://github.com/bluejamesbond/TextJustify-Android
SUPPORTS: Android 2.0 to 5.X; String/Spannables; RTL language support! NO WEBVIEW :)
SCREENSHOT
Try this:
Add a TextViewJustify.java file in src folder.
TextViewJustify.java wil be like this
import android.graphics.Paint;
import android.view.Gravity;
import android.widget.TextView;
public class TextViewJustify {
/*
* PLEASE DO NOT REMOVE Coded by Mathew Kurian I wrote this code for a
* Google Interview for Internship. Unfortunately, I got too nervous during
* the interview that I messed, but anyhow that doesn't matter. I have
* resent my work in hopes that I might still get a position there. Thank
* you :DD
*/
final static String SYSTEM_NEWLINE = "\n";
final static float COMPLEXITY = 5.12f; // Reducing this will increase
// efficiency but will decrease
// effectiveness
final static Paint p = new Paint();
public static void justifyText(final TextView tv, final float origWidth) {
String s = tv.getText().toString();
p.setTypeface(tv.getTypeface());
String[] splits = s.split(SYSTEM_NEWLINE);
float width = origWidth - 5;
for (int x = 0; x < splits.length; x++)
if (p.measureText(splits[x]) > width) {
splits[x] = wrap(splits[x], width, p);
String[] microSplits = splits[x].split(SYSTEM_NEWLINE);
for (int y = 0; y < microSplits.length - 1; y++)
microSplits[y] = justify(removeLast(microSplits[y], " "),
width, p);
StringBuilder smb_internal = new StringBuilder();
for (int z = 0; z < microSplits.length; z++)
smb_internal.append(microSplits[z]
+ ((z + 1 < microSplits.length) ? SYSTEM_NEWLINE
: ""));
splits[x] = smb_internal.toString();
}
final StringBuilder smb = new StringBuilder();
for (String cleaned : splits)
smb.append(cleaned + SYSTEM_NEWLINE);
tv.setGravity(Gravity.LEFT);
tv.setText(smb);
}
private static String wrap(String s, float width, Paint p) {
String[] str = s.split("\\s"); // regex
StringBuilder smb = new StringBuilder(); // save memory
smb.append(SYSTEM_NEWLINE);
for (int x = 0; x < str.length; x++) {
float length = p.measureText(str[x]);
String[] pieces = smb.toString().split(SYSTEM_NEWLINE);
try {
if (p.measureText(pieces[pieces.length - 1]) + length > width)
smb.append(SYSTEM_NEWLINE);
} catch (Exception e) {
}
smb.append(str[x] + " ");
}
return smb.toString().replaceFirst(SYSTEM_NEWLINE, "");
}
private static String removeLast(String s, String g) {
if (s.contains(g)) {
int index = s.lastIndexOf(g);
int indexEnd = index + g.length();
if (index == 0)
return s.substring(1);
else if (index == s.length() - 1)
return s.substring(0, index);
else
return s.substring(0, index) + s.substring(indexEnd);
}
return s;
}
private static String justifyOperation(String s, float width, Paint p) {
float holder = (float) (COMPLEXITY * Math.random());
while (s.contains(Float.toString(holder)))
holder = (float) (COMPLEXITY * Math.random());
String holder_string = Float.toString(holder);
float lessThan = width;
int timeOut = 100;
int current = 0;
while (p.measureText(s) < lessThan && current < timeOut) {
s = s.replaceFirst(" ([^" + holder_string + "])", " "
+ holder_string + "$1");
lessThan = p.measureText(holder_string) + lessThan
- p.measureText(" ");
current++;
}
String cleaned = s.replaceAll(holder_string, " ");
return cleaned;
}
private static String justify(String s, float width, Paint p) {
while (p.measureText(s) < width) {
s = justifyOperation(s, width, p);
}
return s;
}
}
And use this class like this:
TextViewJustify.justifyText(your_text_view, 225f);
In my case it was 225f. change it according to your need.
You can justify Text using WebView Simply
LinearLayout lv=(LinearLayout)dialog.findViewById(R.id.**yourId**);
String text1 = "<html><body>"
+ "<p align=\"justify\">"
+**your text**
+ "</p> "
+ "</body></html>";
WebView wv=new WebView(getApplicationContext());
wv.loadData(text1,"text/html","utf-8");
lv.removeAllViews();
lv.addView(wv);
i made simple class.
this can be used just like TextView
import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* text justifying
* you can just use like TextView
* #author hyunsikkim
*
*/
public class JustifiedTextView extends TextView {
public JustifiedTextView(Context context) {
super(context);
}
public JustifiedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void setBreakText(String text) {
if(text == null) return;
String breakText = breakText(getPaint(), text,
getWidth()-this.getPaddingLeft()-this.getPaddingRight());
if(breakText.equals(getText()) == false) {
setText(breakText);
}
}
public String breakText(Paint textPaint, String strText, int breakWidth) {
StringBuilder sb = new StringBuilder();
int endValue = 0;
final String NEW_LINE = "\n";
do{
endValue = textPaint.breakText(strText, true, breakWidth, null);
if(endValue > 0) {
/**
* handle if text contains NEW_LINE
*/
final int index = strText.indexOf(NEW_LINE);
if(0<=index && index <= endValue) {
endValue = index + NEW_LINE.length();
}
final String sub = strText.substring(0, endValue);
sb.append(sub);
/**
* handle breaked text endWidth NEW_LINE
*/
if(sub.endsWith(NEW_LINE) == false) {
if(strText.length() != endValue) {
sb.append(NEW_LINE);
}
}
strText = strText.substring(endValue);
}
} while(endValue > 0);
return sb.toString();
}
public String breakText(Paint textPaint, int id, int breakWidth) {
String strText = getResources().getString(id);
return breakText(textPaint, strText, breakWidth);
}
#Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
/**
* this control changes from setText(Charsequence text)
*/
if(getWidth() != 0) {
setBreakText(text.toString());
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
/**
* this help to break initial text.
*/
if(w != oldw) {
setBreakText(getText().toString());
}
}
}
Use web view
WebView tv = (WebView) findViewById(R.id.aboutme);
String youtContentStr = String.valueOf(Html
.fromHtml("<![CDATA[<body style=\"text-align:justify;background-color:#00222222;\">"
+ text
+ "</body>]]>"));
tv.setBackgroundColor(Color.TRANSPARENT);
tv.loadData(youtContentStr, "text/html", "utf-8");`
I would like to improve my fork/join little example to show that during Java Fork/Join framework execution work stealing occurs.
What changes I need to do to following code? Purpose of example: just do a linear research of a value breaking up work between multiple threads.
package com.stackoverflow.questions;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class CounterFJ<T extends Comparable<T>> extends RecursiveTask<Integer> {
private static final long serialVersionUID = 5075739389907066763L;
private List<T> _list;
private T _test;
private int _lastCount = -1;
private int _start;
private int _end;
private int _divideFactor = 4;
private static final int THRESHOLD = 20;
public CounterFJ(List<T> list, T test, int start, int end, int factor) {
_list = list;
_test = test;
_start = start;
_end = end;
_divideFactor = factor;
}
public CounterFJ(List<T> list, T test, int factor) {
this(list, test, 0, list.size(), factor);
}
#Override
protected Integer compute() {
if (_end - _start < THRESHOLD) {
int count = 0;
for (int i = _start; i < _end; i++) {
if (_list.get(i).compareTo(_test) == 0) {
count++;
}
}
_lastCount = count;
return new Integer(count);
}
LinkedList<CounterFJ<T>> taskList = new LinkedList<>();
int step = (_end - _start) / _divideFactor;
for (int j = 0; j < _divideFactor; j++) {
CounterFJ<T> task = null;
if (j == 0)
task = new CounterFJ<T>(_list, _test, _start, _start + step, _divideFactor);
else if (j == _divideFactor - 1)
task = new CounterFJ<T>(_list, _test, _start + (step * j), _end, _divideFactor);
else
task = new CounterFJ<T>(_list, _test, _start + (step * j), _start + (step * (j + 1)), _divideFactor);
// task.fork();
taskList.add(task);
}
invokeAll(taskList);
_lastCount = 0;
for (CounterFJ<T> task : taskList) {
_lastCount += task.join();
}
return new Integer(_lastCount);
}
public int getResult() {
return _lastCount;
}
public static void main(String[] args) {
LinkedList<Long> list = new LinkedList<Long>();
long range = 200;
Random r = new Random(42);
for (int i = 0; i < 1000; i++) {
list.add(new Long((long) (r.nextDouble() * range)));
}
CounterFJ<Long> counter = new CounterFJ<>(list, new Long(100), 4);
ForkJoinPool pool = new ForkJoinPool();
long time = System.currentTimeMillis();
pool.invoke(counter);
System.out.println("Fork join counter in " + (System.currentTimeMillis() - time));
System.out.println("Occurrences:" + counter.getResult());
}
}
Finally I managed how to and it's not difficult so I leave this for future readers.
In the costructor of the RecursiveTask save thread that created the instance itself. In the compute method check if executing thread is the same or not. If not work-stealing has occurred.
So I added this member variable
private long _threadId = -1;
private static int stolen_tasks = 0;
changed constructor like this:
public CounterFJ(List<T> list, T test, int start, int end, int factor) {
_list = list;
_threadId = Thread.currentThread().getId(); //added
_test = test;
_start = start;
_end = end;
_branchFactor = factor;
}
and added comparison into compute method:
#Override
protected Integer compute() {
long thisThreadId = Thread.currentThread().getId();
if (_threadId != thisThreadId){
stolen_tasks++;
}
// rest of the method