Related
Currently, I've been asked to write CNN code using DL4J using YOLOv2 architecture. But the problem is after the model has complete, I do a simple GUI for validation testing then the image shown is too bright and sometimes the image can be displayed. Im not sure where does this problem comes from whether at earliest stage of training or else. Here, I attach the code that I have for now. For Iterator:
public class faceMaskIterator {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(faceMaskIterator.class);
private static final int seed = 123;
private static Random rng = new Random(seed);
private static String dataDir;
private static Path pathDirectory;
private static InputSplit trainData, testData;
private static final String[] allowedFormats = NativeImageLoader.ALLOWED_FORMATS;
private static final double splitRatio = 0.8;
private static final int nChannels = 3;
public static final int gridWidth = 13;
public static final int gridHeight = 13;
public static final int yolowidth = 416;
public static final int yoloheight = 416;
private static RecordReaderDataSetIterator makeIterator(InputSplit split, Path dir, int batchSize) throws Exception {
ObjectDetectionRecordReader recordReader = new ObjectDetectionRecordReader(yoloheight, yolowidth, nChannels,
gridHeight, gridWidth, new VocLabelProvider(dir.toString()));
recordReader.initialize(split);
RecordReaderDataSetIterator iter = new RecordReaderDataSetIterator(recordReader, batchSize, 1, 1,true);
iter.setPreProcessor(new ImagePreProcessingScaler(0, 1));
return iter;
}
public static RecordReaderDataSetIterator trainIterator(int batchSize) throws Exception {
return makeIterator(trainData, pathDirectory, batchSize);
}
public static RecordReaderDataSetIterator testIterator(int batchSize) throws Exception {
return makeIterator(testData, pathDirectory, batchSize);
}
public static void setup() throws IOException {
log.info("Load data...");
dataDir = Paths.get(
System.getProperty("user.home"),
Helper.getPropValues("dl4j_home.data")
).toString();
pathDirectory = Paths.get(dataDir,"face_mask_dataset");
FileSplit fileSplit = new FileSplit(new File(pathDirectory.toString()),allowedFormats,rng);
PathFilter pathFilter = new RandomPathFilter(rng,allowedFormats);
InputSplit[] sample = fileSplit.sample(pathFilter, splitRatio,1-splitRatio);
trainData = sample[0];
testData = sample[1];
}}
For training:
public class faceMaskPreTrained {
private static final Logger log = LoggerFactory.getLogger(ai.certifai.groupProjek.faceMaskPreTrained.class);
private static int seed = 420;
private static double detectionThreshold = 0.9;
private static int nBoxes = 3;
private static double lambdaNoObj = 0.7;
private static double lambdaCoord = 1.0;
private static double[][] priorBoxes = {{1, 1}, {2, 1}, {1, 2}};
private static int batchSize = 3;
private static int nEpochs = 1;
private static double learningRate = 1e-4;
private static int nClasses = 3;
private static List<String> labels;
private static File modelFilename = new File(System.getProperty("user.dir"), "generated-models/facemask_detector.zip");
private static ComputationGraph model;
private static Frame frame = null;
private static final Scalar GREEN = RGB(0, 255.0, 0);
private static final Scalar YELLOW = RGB(255, 255, 0);
private static final Scalar RED = RGB(255, 0, 0);
private static Scalar[] colormap = {GREEN, YELLOW, RED};
private static String labeltext = null;
public static void main(String[] args) throws Exception {
faceMaskIterator.setup();
RecordReaderDataSetIterator trainIter = faceMaskIterator.trainIterator(batchSize);
RecordReaderDataSetIterator testIter = faceMaskIterator.testIterator(1);
labels = trainIter.getLabels();
if (modelFilename.exists()) {
Nd4j.getRandom().setSeed(seed);
log.info("Load model...");
model = ModelSerializer.restoreComputationGraph(modelFilename);
} else {
Nd4j.getRandom().setSeed(seed);
INDArray priors = Nd4j.create(priorBoxes);
log.info("Build model...");
ComputationGraph pretrained = (ComputationGraph) YOLO2.builder().build().initPretrained();
FineTuneConfiguration fineTuneConf = getFineTuneConfiguration();
model = getComputationGraph(pretrained, priors, fineTuneConf);
System.out.println(model.summary(InputType.convolutional(
faceMaskIterator.yoloheight,
faceMaskIterator.yolowidth,
nClasses)));
log.info("Train model...");
UIServer server = UIServer.getInstance();
StatsStorage storage = new InMemoryStatsStorage();
server.attach(storage);
model.setListeners(new ScoreIterationListener(5), new StatsListener(storage,5));
for (int i = 1; i < nEpochs + 1; i++) {
trainIter.reset();
while (trainIter.hasNext()) {
model.fit(trainIter.next());
}
log.info("*** Completed epoch {} ***", i);
}
ModelSerializer.writeModel(model, modelFilename, true);
System.out.println("Model saved.");
}
// Evaluate the model's accuracy by using the test iterator.
OfflineValidationWithTestDataset(testIter);
// Inference the model and process the webcam stream and make predictions.
doInference();
}
private static ComputationGraph getComputationGraph(ComputationGraph pretrained, INDArray priors, FineTuneConfiguration fineTuneConf) {
return new TransferLearning.GraphBuilder(pretrained)
.fineTuneConfiguration(fineTuneConf)
.removeVertexKeepConnections("conv2d_23")
.removeVertexKeepConnections("outputs")
.addLayer("conv2d_23",
new ConvolutionLayer.Builder(1, 1)
.nIn(1024)
.nOut(nBoxes * (5 + nClasses))
.stride(1, 1)
.convolutionMode(ConvolutionMode.Same)
.weightInit(WeightInit.XAVIER)
.activation(Activation.IDENTITY)
.build(),
"leaky_re_lu_22")
.addLayer("outputs",
new Yolo2OutputLayer.Builder()
.lambdaNoObj(lambdaNoObj)
.lambdaCoord(lambdaCoord)
.boundingBoxPriors(priors.castTo(DataType.FLOAT))
.build(),
"conv2d_23")
.setOutputs("outputs")
.build();
}
private static FineTuneConfiguration getFineTuneConfiguration() {
return new FineTuneConfiguration.Builder()
.seed(seed)
.optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
.gradientNormalization(GradientNormalization.RenormalizeL2PerLayer)
.gradientNormalizationThreshold(1.0)
.updater(new Adam.Builder().learningRate(learningRate).build())
.l2(0.00001)
.activation(Activation.IDENTITY)
.trainingWorkspaceMode(WorkspaceMode.ENABLED)
.inferenceWorkspaceMode(WorkspaceMode.ENABLED)
.build();
}
// Evaluate visually the performance of the trained object detection model
private static void OfflineValidationWithTestDataset(RecordReaderDataSetIterator test) throws InterruptedException {
NativeImageLoader imageLoader = new NativeImageLoader();
CanvasFrame canvas = new CanvasFrame("Validate Test Dataset");
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
org.deeplearning4j.nn.layers.objdetect.Yolo2OutputLayer yout = (org.deeplearning4j.nn.layers.objdetect.Yolo2OutputLayer) model.getOutputLayer(0);
Mat convertedMat = new Mat();
Mat convertedMat_big = new Mat();
while (test.hasNext() && canvas.isVisible()) {
org.nd4j.linalg.dataset.DataSet ds = test.next();
INDArray features = ds.getFeatures();
INDArray results = model.outputSingle(features);
List<DetectedObject> objs = yout.getPredictedObjects(results, detectionThreshold);
YoloUtils.nms(objs, 0.4);
Mat mat = imageLoader.asMat(features);
mat.convertTo(convertedMat, CV_8U, 255, 0);
int w = mat.cols() * 2;
int h = mat.rows() * 2;
resize(convertedMat, convertedMat_big, new Size(w, h));
convertedMat_big = drawResults(objs, convertedMat_big, w, h);
canvas.showImage(converter.convert(convertedMat_big));
canvas.waitKey();
}
canvas.dispose();
}
// Stream video frames from Webcam and run them through YOLOv2 model and get predictions
private static void doInference() {
String cameraPos = "front";
int cameraNum = 0;
Thread thread = null;
NativeImageLoader loader = new NativeImageLoader(
faceMaskIterator.yolowidth,
faceMaskIterator.yoloheight,
3,
new ColorConversionTransform(COLOR_BGR2RGB));
ImagePreProcessingScaler scaler = new ImagePreProcessingScaler(0, 1);
if (!cameraPos.equals("front") && !cameraPos.equals("back")) {
try {
throw new Exception("Unknown argument for camera position. Choose between front and back");
} catch (Exception e) {
e.printStackTrace();
}
}
FrameGrabber grabber = null;
try {
grabber = FrameGrabber.createDefault(cameraNum);
} catch (FrameGrabber.Exception e) {
e.printStackTrace();
}
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
try {
grabber.start();
} catch (FrameGrabber.Exception e) {
e.printStackTrace();
}
CanvasFrame canvas = new CanvasFrame("Object Detection");
int w = grabber.getImageWidth();
int h = grabber.getImageHeight();
canvas.setCanvasSize(w, h);
while (true) {
try {
frame = grabber.grab();
} catch (FrameGrabber.Exception e) {
e.printStackTrace();
}
//if a thread is null, create new thread
if (thread == null) {
thread = new Thread(() ->
{
while (frame != null) {
try {
Mat rawImage = new Mat();
//Flip the camera if opening front camera
if (cameraPos.equals("front")) {
Mat inputImage = converter.convert(frame);
flip(inputImage, rawImage, 1);
} else {
rawImage = converter.convert(frame);
}
Mat resizeImage = new Mat();
resize(rawImage, resizeImage, new Size(faceMaskIterator.yolowidth, faceMaskIterator.yoloheight));
INDArray inputImage = loader.asMatrix(resizeImage);
scaler.transform(inputImage);
INDArray outputs = model.outputSingle(inputImage);
org.deeplearning4j.nn.layers.objdetect.Yolo2OutputLayer yout = (org.deeplearning4j.nn.layers.objdetect.Yolo2OutputLayer) model.getOutputLayer(0);
List<DetectedObject> objs = yout.getPredictedObjects(outputs, detectionThreshold);
YoloUtils.nms(objs, 0.4);
rawImage = drawResults(objs, rawImage, w, h);
canvas.showImage(converter.convert(rawImage));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
thread.start();
}
KeyEvent t = null;
try {
t = canvas.waitKey(33);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((t != null) && (t.getKeyCode() == KeyEvent.VK_Q)) {
break;
}
}
}
private static Mat drawResults(List<DetectedObject> objects, Mat mat, int w, int h) {
for (DetectedObject obj : objects) {
double[] xy1 = obj.getTopLeftXY();
double[] xy2 = obj.getBottomRightXY();
String label = labels.get(obj.getPredictedClass());
int x1 = (int) Math.round(w * xy1[0] / faceMaskIterator.gridWidth);
int y1 = (int) Math.round(h * xy1[1] / faceMaskIterator.gridHeight);
int x2 = (int) Math.round(w * xy2[0] / faceMaskIterator.gridWidth);
int y2 = (int) Math.round(h * xy2[1] / faceMaskIterator.gridHeight);
//Draw bounding box
rectangle(mat, new Point(x1, y1), new Point(x2, y2), colormap[obj.getPredictedClass()], 2, 0, 0);
//Display label text
labeltext = label + " " + String.format("%.2f", obj.getConfidence() * 100) + "%";
int[] baseline = {0};
Size textSize = getTextSize(labeltext, FONT_HERSHEY_DUPLEX, 1, 1, baseline);
rectangle(mat, new Point(x1 + 2, y2 - 2), new Point(x1 + 2 + textSize.get(0), y2 - 2 - textSize.get(1)), colormap[obj.getPredictedClass()], FILLED, 0, 0);
putText(mat, labeltext, new Point(x1 + 2, y2 - 2), FONT_HERSHEY_DUPLEX, 1, RGB(0, 0, 0));
}
return mat;
}
CanvasFrame tries to do gamma correction by default because it's typically needed by cameras used for CV, but cheap webcams usually output gamma corrected images, so make sure to let CanvasFrame know about it this way:
// We should also specify the relative monitor/camera response for proper gamma correction.
CanvasFrame frame = new CanvasFrame("Some Title", CanvasFrame.getDefaultGamma()/grabber.getGamma());
https://github.com/bytedeco/javacv/
We have been using the iText based PdfVeryDenseMergeTool we found in this SO question How To Remove Whitespace on Merge to merge multiple PDF files into a single PDF file. The tool merges PDFs without leaving any whitespace in between, and individual PDFs also get broken out across pages when possible.
We want to port PdfVeryDenseMergeTool to PDFBox. We found a PDFBox 2 based PdfDenseMergeTool that merges PDFs like this:
Individual PDFs:
Dense Merged PDF:
We are looking for something like this (this is already one in iText based PdfVeryDenseMergeTool but we want to do it using PDFBox 2) :
In our attempt to do the porting, we found that PdfVeryDenseMergeTool uses a PageVerticalAnalyzer that extends iText PDF Render Listener and does something every time a text, image, or arc is drawn in a PDF. And all the rendering info is then used to split an individual PDF across multiple pages. We tried looking for a similar PDF Render Listener in PDFBox 2 but found that the available PDFRenderer class only has image rendering methods. So we are not sure how to port PageVerticalAnalyzer to PDFBox.
If someone can suggest an approach to move forward, we'd greatly appreciate their help.
Thanks a lot!
EDIT 7 Feb 2020
At present, we are extending PDFGraphicsStreamEngine from PDFBox to make a custom rendering engine that tracks coordinates of images, text lines, and arcs when they are drawn. That custom engine will be the port of the PageVerticalAnalyzer. After that, we are hoping to be able to port PdfVeryDenseMergeTool to PDFBox.
EDIT 8 Feb 2020
Here is a very simple port of PageVerticalAnalyzer that handles images and text. I'm a PDFBox newbie, so my logic to handle images is probably wonky. Here's the basic approach:
Text: for every glyph printed, get the bottomY and make topY = bottomY + charHeight, mark those top/bottom points.
Image: for every call to drawImage(), it looks like there are two ways to figure out where it was drawn. First is using the coords from the last call to appendRectangle() and second is using the last calls to moveTo(), multiple lineTo(), and closePath(). I give the latter one priority. If I can't find any path (I found it in one PDF, in another, before drawImage(), I only found appendRectangle()), I use the former. If none of them exist, I have no clue what to do. Here's how I'm assuming PDFBox marks image coords using moveTo()/lineTo()/closePath():
Here is my current implementation:
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.Vector;
public class PageVerticalAnalyzer extends PDFGraphicsStreamEngine
{
/**
* This is a port of iText based PageVerticalAnalyzer found here
* https://github.com/mkl-public/testarea-itext5/blob/master/src/main/java/mkl/testarea/itext5/merge/PageVerticalAnalyzer.java
*
* #param page PDF Page
*/
protected PageVerticalAnalyzer(PDPage page)
{
super(page);
}
public static void main(String[] args) throws IOException
{
File file = new File("q2.pdf");
try (PDDocument doc = PDDocument.load(file))
{
PDPage page = doc.getPage(0);
PageVerticalAnalyzer engine = new PageVerticalAnalyzer(page);
engine.run();
System.out.println(engine.verticalFlips);
}
}
/**
* Runs the engine on the current page.
*
* #throws IOException If there is an IO error while drawing the page.
*/
public void run() throws IOException
{
processPage(getPage());
for (PDAnnotation annotation : getPage().getAnnotations())
{
showAnnotation(annotation);
}
}
// All path related stuff
#Override
public void clip(int windingRule) throws IOException
{
System.out.println("clip");
}
#Override
public void moveTo(float x, float y) throws IOException
{
System.out.printf("moveTo %.2f %.2f%n", x, y);
lastPathBottomTop = new float[] {(Float) null, y};
}
#Override
public void lineTo(float x, float y) throws IOException
{
System.out.printf("lineTo %.2f %.2f%n", x, y);
lastLineTo = new float[] {x, y};
}
#Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
System.out.printf("curveTo %.2f %.2f, %.2f %.2f, %.2f %.2f%n", x1, y1, x2, y2, x3, y3);
}
#Override
public Point2D getCurrentPoint() throws IOException
{
// if you want to build paths, you'll need to keep track of this like PageDrawer does
return new Point2D.Float(0, 0);
}
#Override
public void closePath() throws IOException
{
System.out.println("closePath");
lastPathBottomTop[0] = lastLineTo[1];
lastLineTo = null;
}
#Override
public void endPath() throws IOException
{
System.out.println("endPath");
}
#Override
public void strokePath() throws IOException
{
System.out.println("strokePath");
}
#Override
public void fillPath(int windingRule) throws IOException
{
System.out.println("fillPath");
}
#Override
public void fillAndStrokePath(int windingRule) throws IOException
{
System.out.println("fillAndStrokePath");
}
#Override
public void shadingFill(COSName shadingName) throws IOException
{
System.out.println("shadingFill " + shadingName.toString());
}
// Rectangle related stuff
#Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
{
System.out.printf("appendRectangle %.2f %.2f, %.2f %.2f, %.2f %.2f, %.2f %.2f%n",
p0.getX(), p0.getY(), p1.getX(), p1.getY(),
p2.getX(), p2.getY(), p3.getX(), p3.getY());
lastRectBottomTop = new float[] {(float) p0.getY(), (float) p3.getY()};
}
// Image drawing
#Override
public void drawImage(PDImage pdImage) throws IOException
{
System.out.println("drawImage");
if (lastPathBottomTop != null) {
addVerticalUseSection(lastPathBottomTop[0], lastPathBottomTop[1]);
} else if (lastRectBottomTop != null ){
addVerticalUseSection(lastRectBottomTop[0], lastRectBottomTop[1]);
} else {
throw new Error("Drawing image without last reference!");
}
lastPathBottomTop = null;
lastRectBottomTop = null;
}
// All text related stuff
#Override
public void showTextString(byte[] string) throws IOException
{
System.out.print("showTextString \"");
super.showTextString(string);
System.out.println("\"");
}
#Override
public void showTextStrings(COSArray array) throws IOException
{
System.out.print("showTextStrings \"");
super.showTextStrings(array);
System.out.println("\"");
}
#Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode,
Vector displacement) throws IOException
{
// print the actual character that is being rendered
System.out.print(unicode);
super.showGlyph(textRenderingMatrix, font, code, unicode, displacement);
// rendering matrix seems to contain bounding box of dimensions the char
// and an x/y point where bounding box starts
//System.out.println(textRenderingMatrix.toString());
// y of the bottom of the char
// not sure why the y value is in the 8th column
// when I print the matrix, it shows up in the 6th column
float yBottom = textRenderingMatrix.getValue(0, 7);
// height of the char
// using the value in the first column as the char height
float yTop = yBottom + textRenderingMatrix.getValue(0, 0);
addVerticalUseSection(yBottom, yTop);
}
// Keeping track of bottom/top point pairs
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>();
private float[] lastRectBottomTop;
private float[] lastPathBottomTop;
private float[] lastLineTo;
}
I am looking for answers to the following questions:
How can I improve this implementation?
How to handle other things like curves that I have not handled?
This answer suffers from the same issues as the original iText version does.
A port of the PageVerticalAnalyzer
One can port the PageVerticalAnalyzer as follows from iText to PDFBox:
public class PageVerticalAnalyzer extends PDFGraphicsStreamEngine {
protected PageVerticalAnalyzer(PDPage page) {
super(page);
}
public List<Float> getVerticalFlips() {
return verticalFlips;
}
//
// Text
//
#Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode, Vector displacement)
throws IOException {
super.showGlyph(textRenderingMatrix, font, code, unicode, displacement);
Shape shape = calculateGlyphBounds(textRenderingMatrix, font, code);
if (shape != null) {
Rectangle2D rect = shape.getBounds2D();
addVerticalUseSection(rect.getMinY(), rect.getMaxY());
}
}
/**
* Copy of <code>org.apache.pdfbox.examples.util.DrawPrintTextLocations.calculateGlyphBounds(Matrix, PDFont, int)</code>.
*/
private Shape calculateGlyphBounds(Matrix textRenderingMatrix, PDFont font, int code) throws IOException
{
GeneralPath path = null;
AffineTransform at = textRenderingMatrix.createAffineTransform();
at.concatenate(font.getFontMatrix().createAffineTransform());
if (font instanceof PDType3Font)
{
// It is difficult to calculate the real individual glyph bounds for type 3 fonts
// because these are not vector fonts, the content stream could contain almost anything
// that is found in page content streams.
PDType3Font t3Font = (PDType3Font) font;
PDType3CharProc charProc = t3Font.getCharProc(code);
if (charProc != null)
{
BoundingBox fontBBox = t3Font.getBoundingBox();
PDRectangle glyphBBox = charProc.getGlyphBBox();
if (glyphBBox != null)
{
// PDFBOX-3850: glyph bbox could be larger than the font bbox
glyphBBox.setLowerLeftX(Math.max(fontBBox.getLowerLeftX(), glyphBBox.getLowerLeftX()));
glyphBBox.setLowerLeftY(Math.max(fontBBox.getLowerLeftY(), glyphBBox.getLowerLeftY()));
glyphBBox.setUpperRightX(Math.min(fontBBox.getUpperRightX(), glyphBBox.getUpperRightX()));
glyphBBox.setUpperRightY(Math.min(fontBBox.getUpperRightY(), glyphBBox.getUpperRightY()));
path = glyphBBox.toGeneralPath();
}
}
}
else if (font instanceof PDVectorFont)
{
PDVectorFont vectorFont = (PDVectorFont) font;
path = vectorFont.getPath(code);
if (font instanceof PDTrueTypeFont)
{
PDTrueTypeFont ttFont = (PDTrueTypeFont) font;
int unitsPerEm = ttFont.getTrueTypeFont().getHeader().getUnitsPerEm();
at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
}
if (font instanceof PDType0Font)
{
PDType0Font t0font = (PDType0Font) font;
if (t0font.getDescendantFont() instanceof PDCIDFontType2)
{
int unitsPerEm = ((PDCIDFontType2) t0font.getDescendantFont()).getTrueTypeFont().getHeader().getUnitsPerEm();
at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
}
}
}
else if (font instanceof PDSimpleFont)
{
PDSimpleFont simpleFont = (PDSimpleFont) font;
// these two lines do not always work, e.g. for the TT fonts in file 032431.pdf
// which is why PDVectorFont is tried first.
String name = simpleFont.getEncoding().getName(code);
path = simpleFont.getPath(name);
}
else
{
// shouldn't happen, please open issue in JIRA
System.out.println("Unknown font class: " + font.getClass());
}
if (path == null)
{
return null;
}
return at.createTransformedShape(path.getBounds2D());
}
//
// Bitmaps
//
#Override
public void drawImage(PDImage pdImage) throws IOException {
Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
Section section = null;
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
Point2D.Float point = ctm.transformPoint(x, y);
if (section == null)
section = new Section(point.y);
else
section.extendTo(point.y);
}
}
addVerticalUseSection(section.from, section.to);
}
//
// Paths
//
#Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
subPath = null;
Section section = new Section(p0.getY());
section.extendTo(p1.getY()).extendTo(p2.getY()).extendTo(p3.getY());
currentPoint = p0;
}
#Override
public void clip(int windingRule) throws IOException {
}
#Override
public void moveTo(float x, float y) throws IOException {
subPath = new Section(y);
path.add(subPath);
currentPoint = new Point2D.Float(x, y);
}
#Override
public void lineTo(float x, float y) throws IOException {
if (subPath == null) {
subPath = new Section(y);
path.add(subPath);
} else
subPath.extendTo(y);
currentPoint = new Point2D.Float(x, y);
}
/**
* Beware! This is incorrect! The control points may be outside
* the vertically used range
*/
#Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
if (subPath == null) {
subPath = new Section(y1);
path.add(subPath);
} else
subPath.extendTo(y1);
subPath.extendTo(y2).extendTo(y3);
currentPoint = new Point2D.Float(x3, y3);
}
#Override
public Point2D getCurrentPoint() throws IOException {
return currentPoint;
}
#Override
public void closePath() throws IOException {
}
#Override
public void endPath() throws IOException {
path.clear();
subPath = null;
}
#Override
public void strokePath() throws IOException {
for (Section section : path) {
addVerticalUseSection(section.from, section.to);
}
path.clear();
subPath = null;
}
#Override
public void fillPath(int windingRule) throws IOException {
for (Section section : path) {
addVerticalUseSection(section.from, section.to);
}
path.clear();
subPath = null;
}
#Override
public void fillAndStrokePath(int windingRule) throws IOException {
for (Section section : path) {
addVerticalUseSection(section.from, section.to);
}
path.clear();
subPath = null;
}
#Override
public void shadingFill(COSName shadingName) throws IOException {
// TODO Auto-generated method stub
}
Point2D currentPoint = null;
List<Section> path = new ArrayList<Section>();
Section subPath = null;
static class Section {
Section(double value) {
this((float)value);
}
Section(float value) {
from = value;
to = value;
}
Section extendTo(double value) {
return extendTo((float)value);
}
Section extendTo(float value) {
if (value < from)
from = value;
else if (value > to)
to = value;
return this;
}
private float from;
private float to;
}
void addVerticalUseSection(double from, double to) {
addVerticalUseSection((float)from, (float)to);
}
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)
The implementation actually is similar to that of the BoundingBoxFinder from this answer. Just like there I borrowed from the PDFBox example DrawPrintTextLocations to determine text outlines.
Furthermore, there is an issue in the curveTo processing corresponding to that of the original iText5 PageVerticalAnalyzer from this answer, the control points are treated as if they were on the actual curve but they actually usually are not and can be far outside the vertical use range of the curve. Instead of the path processing as implemented here one could use corresponding AWT classes but that may not be possible on Android etc.
And just like there this class ignores annotations, but the iText5 dense merger also ignored annotations. And this class also ignores the clip path...
A port of the PdfVeryDenseMergeTool
public class PdfVeryDenseMergeTool {
public PdfVeryDenseMergeTool(PDRectangle 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<PDDocument> inputs) throws IOException
{
try
{
openDocument();
for (PDDocument input: inputs)
{
merge(input);
}
if (currentContents != null) {
currentContents.close();
currentContents = null;
}
document.save(outputStream);
}
finally
{
closeDocument();
}
}
void openDocument() throws IOException
{
document = new PDDocument();
newPage();
}
void closeDocument() throws IOException
{
try
{
if (currentContents != null) {
currentContents.close();
currentContents = null;
}
document.close();
}
finally
{
this.document = null;
this.yPosition = 0;
}
}
void newPage() throws IOException
{
if (currentContents != null) {
currentContents.close();
currentContents = null;
}
currentPage = new PDPage(pageSize);
document.addPage(currentPage);
yPosition = pageSize.getUpperRightY() - topMargin;
currentContents = new PDPageContentStream(document, currentPage);
}
void merge(PDDocument input) throws IOException
{
for (PDPage page : input.getPages())
{
merge(input, page);
}
}
void merge(PDDocument sourceDoc, PDPage page) throws IOException
{
PDRectangle pageSizeToImport = page.getCropBox();
PageVerticalAnalyzer analyzer = new PageVerticalAnalyzer(page);
analyzer.processPage(page);
List<Float> verticalFlips = analyzer.getVerticalFlips();
if (verticalFlips.size() < 2)
return;
LayerUtility layerUtility = new LayerUtility(document);
PDFormXObject form = layerUtility.importPageAsForm(sourceDoc, page);
int startFlip = verticalFlips.size() - 1;
boolean first = true;
while (startFlip > 0)
{
if (!first)
newPage();
float freeSpace = yPosition - pageSize.getLowerLeftY() - bottomMargin;
int endFlip = startFlip + 1;
while ((endFlip > 1) && (verticalFlips.get(startFlip) - verticalFlips.get(endFlip - 2) < freeSpace))
endFlip -=2;
if (endFlip < startFlip)
{
float height = verticalFlips.get(startFlip) - verticalFlips.get(endFlip);
currentContents.saveGraphicsState();
currentContents.addRect(0, yPosition - height, pageSizeToImport.getWidth(), height);
currentContents.clip();
Matrix matrix = Matrix.getTranslateInstance(0, (float)(yPosition - (verticalFlips.get(startFlip) - pageSizeToImport.getLowerLeftY())));
currentContents.transform(matrix);
currentContents.drawForm(form);
currentContents.restoreGraphicsState();
yPosition -= height + gap;
startFlip = endFlip - 1;
}
else if (!first)
throw new IllegalArgumentException(String.format("Page %s content sections too large.", page));
first = false;
}
}
PDDocument document = null;
PDPage currentPage = null;
PDPageContentStream currentContents = null;
float yPosition = 0;
final PDRectangle pageSize;
final float topMargin;
final float bottomMargin;
final float gap;
}
(PdfVeryDenseMergeTool.java)
This essentially is a simple port of the iText 5 PdfVeryDenseMergeTool, nothing special about it.
Usage of the PdfVeryDenseMergeTool
One simply creates a PdfVeryDenseMergeTool instance with format information and then starts the merge using PDDocument instances as sources:
PDDocument document1 = ...;
...
PDDocument documentN = ...;
PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(PDRectangle.A4, 30, 30, 10);
tool.merge(new FileOutputStream(RESULT_FILE), Arrays.asList(document1, ..., documentN));
(DenseMerging test testVeryDenseMerging)
How to make the word cloud image of the image.
Like this original Above to output
I am trying with the image to Ascii using https://github.com/bachors/Android-Img2Ascii
TextView textAsc=(TextView)findViewById(R.id.textAsc);
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.step0001);
// Bitmap image = BitmapFactory.decodeFile(filename);
new Img2Ascii()
.bitmap(image)
.quality(4) // 1 - 5
.color(true)
.convert(new Img2Ascii.Listener() {
#Override
public void onProgress(int percentage) {
textAsc.setText(String.valueOf(percentage) + " %");
}
#Override
public void onResponse(Spannable text) {
textAsc.setText(text);
}
});
img2ascii.java
package com.bachors.img2ascii;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static java.lang.Math.round;
/**
* Created by Bachors on 10/31/2017.
* https://github.com/bachors/Android-Img2Ascii
*/
public class Img2Ascii {
private String[] chars = {"#", "#", "+", "\\", ";", ":", ",", ".", "`", " "};
private Bitmap rgbImage;
private Boolean color = false;
private int quality = 3;
private int qualityColor = 6;
private Spannable response;
private Listener listener;
List<String> list = new ArrayList<String>();
public Img2Ascii(){
}
public Img2Ascii bitmap(Bitmap rgbImage){
this.rgbImage = rgbImage;
return this;
}
public Img2Ascii quality(int quality){
this.quality = quality;
return this;
}
public Img2Ascii color(Boolean color){
this.color = color;
return this;
}
public void convert(Listener listener) {
this.listener = listener;
new InstaApi().execute();
}
#SuppressLint("StaticFieldLeak")
private class InstaApi extends AsyncTask<String, Integer, Void> {
private InstaApi(){
}
#Override
protected void onPreExecute() {
super.onPreExecute();
list.add("Cool");
list.add("G");
list.add("S");
list.add("L");
}
#Override
protected Void doInBackground(String... arg0) {
if(color) {
quality = quality + qualityColor;
if (quality > 5 + qualityColor || quality < 1 + qualityColor)
quality = 3 + qualityColor;
}else{
if (quality > 5 || quality < 1)
quality = 3;
}
String tx;
SpannableStringBuilder span = new SpannableStringBuilder();
int width = rgbImage.getWidth();
int height = rgbImage.getHeight();
int i = 0;
for (int y = 0; y < height; y = y + quality) {
for (int x = 0; x < width; x = x + quality) {
int pixel = rgbImage.getPixel(x, y);
int red = Color.red(pixel);
int green = Color.green(pixel);
int blue = Color.blue(pixel);
if(color) {
Random randomizer = new Random();
String random =list.get(randomizer.nextInt(list.size()));
tx = random;
span.append(tx);
span.setSpan(new ForegroundColorSpan(Color.rgb(red, green, blue)), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}else {
int brightness = red + green + blue;
brightness = round(brightness / (765 / (chars.length - 1)));
tx = chars[brightness];
span.append(tx);
}
i++;
}
tx = "\n";
span.append(tx);
publishProgress(y, height);
i++;
if(isCancelled()) break;
}
response = span;
return null;
}
protected void onProgressUpdate(Integer... progress) {
int current = progress[0];
int total = progress[1];
int percentage = 100 * current / total;
listener.onProgress(percentage);
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
listener.onResponse(response);
}
}
public interface Listener {
void onProgress(int percentage);
void onResponse(Spannable response);
}
}
I want to get the text which I have marked in my pdf file. I iterate over the PdfAnnotation of the PdfPage. The annoation has a method getRectangle() which return a PdfArray. I can't create from the PdfArray an Rectangle runtime-class (object/instance) which has the position and overlays over the marked text of the annotation.
With the Rectangle from annotation I wanto to filter via LocationtextExtratctionStrategy the marked content.
I written the following code to get it with iText:
package biz.hochguertel;
import com.itextpdf.kernel.color.DeviceCmyk;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfTextMarkupAnnotation;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.filter.TextRegionEventFilter;
import com.itextpdf.kernel.pdf.canvas.parser.listener.FilteredEventListener;
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class AppIText {
private String filePath = getClass().getClassLoader().getResource("itext/OCA/549_OCA_Java_SE_7_Programmer_I_Certification.pdf").getFile();
private static String DEST = "demo-output/549_OCA_Java_SE_7_Programmer_I_Certification.pdf";
private PdfDocument pdfDocument;
private PdfDocument pdfWriteDoc;
public void before() throws IOException {
File file = new File(DEST);
file.getParentFile().mkdir();
if (file.exists()) {
file.delete();
}
pdfDocument = new PdfDocument(new PdfReader(filePath));
pdfWriteDoc = new PdfDocument(new PdfWriter(DEST));
}
public static void main(String[] args) throws IOException {
AppIText appIText = new AppIText();
appIText.before();
appIText.process();
appIText.close();
}
private void close() {
pdfDocument.close();
pdfWriteDoc.close();
}
private void process() {
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
PdfPage page = pdfDocument.getPage(i);
List<PdfPage> newPdfPages = pdfDocument.copyPagesTo(i, i, pdfWriteDoc);
PdfPage newPage = null;
if (newPdfPages.size() > 0) {
newPage = newPdfPages.get(0);
}
List<PdfAnnotation> annotations = page.getAnnotations();
for (PdfAnnotation annotation : annotations) {
if (annotation.getContents() != null) {
System.out.println(annotation.getContents());
if (annotation instanceof PdfTextMarkupAnnotation) {
PdfArray rectangleArray = annotation.getRectangle();
double x = ((PdfNumber) rectangleArray.get(0)).getValue();
double y = ((PdfNumber) rectangleArray.get(1)).getValue();
double xWidth = ((PdfNumber) rectangleArray.get(2)).getValue();
double yWidth = ((PdfNumber) rectangleArray.get(3)).getValue();
System.out.println(String.format("x=%s,y=%s,w=%s,h=%s", x, y, xWidth, yWidth));
Rectangle rectangle = new Rectangle((float) x, (float) y, (float) xWidth, (float) yWidth);
PdfCanvas canvas = new PdfCanvas(newPage);
canvas.setFillColor(new DeviceCmyk(1, 0, 0, 0))
.rectangle(rectangle)
.fillStroke()
;
FontFilter fontFilter = new FontFilter(rectangle);
FilteredEventListener listener = new FilteredEventListener();
LocationTextExtractionStrategy extractionStrategy = listener.attachEventListener(new LocationTextExtractionStrategy(), fontFilter);
new PdfCanvasProcessor(listener).processPageContent(page);
String actualText = extractionStrategy.getResultantText();
}
}
}
}
}
}
class RectangleEventHandler implements IEventHandler {
#Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
PdfCanvas canvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdfDoc);
canvas.setFillColor(new DeviceCmyk(1, 0, 0, 0))
.rectangle(new Rectangle(20, 10, 10, 820))
.fillStroke();
}
}
class FontFilter extends TextRegionEventFilter {
public FontFilter(Rectangle filterRect) {
super(filterRect);
}
#Override
public boolean accept(IEventData data, EventType type) {
if (type.equals(EventType.RENDER_TEXT)) {
TextRenderInfo renderInfo = (TextRenderInfo) data;
PdfFont font = renderInfo.getFont();
if (null != font) {
String fontName = font.getFontProgram().getFontNames().getFontName();
return fontName.endsWith("Bold") || fontName.endsWith("Oblique");
}
}
return false;
}
}
How to create an rectangle which matches the marked area to extract only the marked (highlighted) text from the pdf?
Or is there an different way to get the marked text ov an annotation from an pdf?
The following main part of the code above is to apply:
Rectangle rectangle = new Rectangle((float) x, (float) y, (float) xWidth, (float) yWidth);
I found the solution.
The Rectancle calculation:
x: annotation.x
y: annotation.y
width: annotation.width - annotation.x
height: annotation.height - annotation.y
What I get now:
Visual Debug (if LOG_LEVEL >= 100):
Extracted Content:
13:50:01.323 [main] INFO b.h.AppIText - Annotation contents: q(7.1).explain(1)
13:50:01.323 [main] INFO b.h.AppIText - rectangleArray: x=90.0338, y=438.245, w=468.33, h=489.749
13:50:01.323 [main] INFO b.h.AppIText - pageSizeWithRotation: x=0.0, y=0.0, w=531.0, h=666.0, top=666.0, bottom=0.0, left=0.0, right=531.0
13:50:01.337 [main] INFO b.h.AppIText - str: Purpose: A finally block can’t be placed before the catch blocks. <cut here because the book is not free, but I get the complete marked text..>
My fixed code looks now:
package biz.hochguertel;
import com.itextpdf.kernel.color.DeviceCmyk;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfTextMarkupAnnotation;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import com.itextpdf.kernel.pdf.canvas.parser.filter.TextRegionEventFilter;
import com.itextpdf.kernel.pdf.canvas.parser.listener.FilteredTextEventListener;
import com.itextpdf.kernel.pdf.canvas.parser.listener.ITextExtractionStrategy;
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* With the help of the following documentations:
* - http://developers.itextpdf.com/content/best-itext-questions-stackoverview/content-parsing-extraction-and-redaction-text/itext7-how-read-text-specific-position
*/
public class AppIText {
private static final Logger LOGGER = LoggerFactory.getLogger(AppIText.class);
private static int LOG_LEVEL = 0;
private final static int VISUAL_DEBUG = 100;
private String filePath = getClass().getClassLoader().getResource("itext/OCA/393-394,549-550_OCA_Java_SE_7_Programmer_I_Certification.pdf").getFile();
private static String DEST = "demo-output/393-394,549-550_OCA_Java_SE_7_Programmer_I_Certification.pdf";
private PdfDocument pdfDocument;
private PdfDocument pdfWriteDoc;
public void before() throws IOException {
File file = new File(DEST);
file.getParentFile().mkdir();
if (file.exists()) {
file.delete();
}
pdfDocument = new PdfDocument(new PdfReader(filePath));
pdfWriteDoc = new PdfDocument(new PdfWriter(DEST));
}
public static void main(String[] args) throws IOException {
AppIText appIText = new AppIText();
appIText.before();
appIText.process();
}
private void process() {
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
PdfPage page = pdfDocument.getPage(i);
List<PdfPage> newPdfPages = pdfDocument.copyPagesTo(i, i, pdfWriteDoc);
PdfPage newPage = null;
if (newPdfPages.size() > 0) {
newPage = newPdfPages.get(0);
}
List<PdfAnnotation> annotations = page.getAnnotations();
for (PdfAnnotation annotation : annotations) {
if (annotation.getContents() != null) {
System.out.println();
LOGGER.info("Annotation contents: {}", annotation.getContents());
if (annotation instanceof PdfTextMarkupAnnotation) {
PdfArray rectangleArray = annotation.getRectangle();
LOGGER.info("rectangleArray: x={}, y={}, w={}, h={}",
rectangleArray.get(0),
rectangleArray.get(1),
rectangleArray.get(2),
rectangleArray.get(3)
);
Rectangle pageSizeWithRotation = page.getCropBox();
LOGGER.info("pageSizeWithRotation: x={}, y={}, w={}, h={}, top={}, bottom={}, left={}, right={}",
pageSizeWithRotation.getX(),
pageSizeWithRotation.getY(),
pageSizeWithRotation.getWidth(),
pageSizeWithRotation.getHeight(),
pageSizeWithRotation.getTop(),
pageSizeWithRotation.getBottom(),
pageSizeWithRotation.getLeft(),
pageSizeWithRotation.getRight()
);
float x = ((PdfNumber) rectangleArray.get(0)).floatValue();
float y = ((PdfNumber) rectangleArray.get(1)).floatValue();
float width = ((PdfNumber) rectangleArray.get(2)).floatValue() - x;
float height = ((PdfNumber) rectangleArray.get(3)).floatValue() - y;
Rectangle rectangle = new Rectangle(
x,
y,
width,
height
);
//13:10:33.097 [main] INFO b.h.AppIText - Annotation contents: q(7.1).explain(1)
//13:10:33.107 [main] INFO b.h.AppIText - rectangleArray: x=90.0338, y=438.245, w=468.33, h=489.749
//13:10:33.107 [main] INFO b.h.AppIText - pageSizeWithRotation: x=0.0, y=0.0, w=531.0, h=666.0, top=666.0, bottom=0.0, left=0.0, right=531.0
//width: 468.33f - 90.0388f,
//height: 489.749f - 438.245f
if (LOG_LEVEL >= VISUAL_DEBUG) {
PdfCanvas canvas = new PdfCanvas(newPage);
canvas.setFillColor(new DeviceCmyk(1, 0, 0, 0))
.rectangle(rectangle)
.fillStroke();
}
TextRegionEventFilter regionFilter = new TextRegionEventFilter(rectangle);
ITextExtractionStrategy strategy = new FilteredTextEventListener(new LocationTextExtractionStrategy(), regionFilter);
String str = PdfTextExtractor.getTextFromPage(page, strategy) + "\n";
LOGGER.info("str: {}", str);
}
}
}
}
pdfDocument.close();
pdfWriteDoc.close();
}
}
Ok so I get this null Pointer exception and neither myself nor my lecturer can figure it out. (he reckons its bad logic and I'm inclined to agree)
I'm making my own 3d object and rendering it with direct world to screenpace co-ords for x and y. This gives me exactly what I want for the excercise and I am able to refine the numbers to get smooth rendering without dropping many frames on my course computer (meaning I cant reproduce the null pointer for my lecturer) but as soon as I run it on beast machine at home the code frames appear to be going much faster, and the cube I'm rendering flashes on and off repeatedly while spitting the null pointer exception to the console(even when I manipulate the numbers to slow it all down). It never crashes though.
Here are the classes that are being used.
Viewport.java(attatched to a JFrame)
package com.my3d.graphics;
import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import com.my3d.geometry.Cube;
import com.my3d.utils.Props;
public class Viewport extends Canvas implements Runnable
{
private static final long serialVersionUID = 1L;
private static final String DEFAULT_NAME = "Viewport";
private static int viewportCount = 0;
private static final BasicStroke stroke = new BasicStroke(0.5f);
private Cube cube;
private boolean running;
private String name;
private int number;
public Viewport()
{
super();
//name the Viewport
if(viewportCount < 10)
setThisName(DEFAULT_NAME +"_0" +viewportCount);
else
setThisName(DEFAULT_NAME +"_" +viewportCount);
//set Identity
setNumber(viewportCount);
//increment the Viewport counter
viewportCount++;
cube = new Cube();
cube.printCubeVertices();
cube.rotateZ(20);
cube.printCubeVertices();
cube.rotateX(20);
cube.printCubeVertices();
running = true;
}
public Viewport(String n)
{
setThisName(n);
viewportCount++;
cube = new Cube();
//cube.printCubeVertices();
//cube.rotateZ(20);
// cube.printCubeVertices();
//cube.rotateX(20);
//cube.printCubeVertices();
running = true;
}
public void tick()
{
//cube.rotateY(0.0001);
//cube.rotateX(0.0001);
repaint();
tock();
}
private void tock()
{
tick();
}
public void setThisName(String n)
{
name = n;
}
private void setNumber(int n)
{
number = n;
}
public Cube getCube()
{
return cube;
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(stroke);
Vertex[] verts = cube.getVerts();
for(int i = 0; i < verts.length; i++)
{
ArrayList<Vertex> links = verts[i].getLinks();
for(int j = 0; j < links.size(); j++)
{
Line2D l = new Line2D.Double(verts[i].getPosition().x()+500,/**/verts[i].getPosition().y()+400,/*|*/links.get(j).getPosition().x()+500,/**/links.get(j).getPosition().y()+400);
g2.setColor(Color.RED);
g2.draw(l);
}
}
}
#Override
public void run()
{
int frame = 0;
while(running)
{
if(frame == 1)
{
cube.rotateY(0.0004);
cube.rotateZ(-0.0005);
cube.rotateX(-0.0003);
}
if(frame > 200000)
{
//cube.rotateY(0.0001);
//cube.rotateZ(0.000015);
repaint();
frame = 0;
}
frame++;
}
}
}
Cube.java
package com.my3d.geometry;
import java.util.ArrayList;
import com.my3d.graphics.Vector3;
import com.my3d.graphics.Vertex;
public class Cube
{
private static final String DEFAULT_NAME = "Cube";
private static final double DEFAULT_SIZE = 100;
private static int cubeCount = 0;
private Vertex[] corners;
private Vector3 position;
private Vector3 rotation;
private String name;
private int number;
public Cube()
{
if(cubeCount < 10)
setName(DEFAULT_NAME +"_0" +cubeCount);
else
setName(DEFAULT_NAME +"_" +cubeCount);
//assign Id
number = cubeCount;
//Increment cube counter
cubeCount++;
corners = new Vertex[8];
for(int i = 0; i < 8; i++)
corners[i] = new Vertex();
corners[0].setPosition(new Vector3(DEFAULT_SIZE,DEFAULT_SIZE,DEFAULT_SIZE));
corners[1].setPosition(new Vector3(-DEFAULT_SIZE,DEFAULT_SIZE,DEFAULT_SIZE));
corners[2].setPosition(new Vector3(-DEFAULT_SIZE,DEFAULT_SIZE,-DEFAULT_SIZE));
corners[3].setPosition(new Vector3(DEFAULT_SIZE,DEFAULT_SIZE,-DEFAULT_SIZE));
corners[4].setPosition(new Vector3(DEFAULT_SIZE,-DEFAULT_SIZE,DEFAULT_SIZE));
corners[5].setPosition(new Vector3(-DEFAULT_SIZE,-DEFAULT_SIZE,DEFAULT_SIZE));
corners[6].setPosition(new Vector3(-DEFAULT_SIZE,-DEFAULT_SIZE,-DEFAULT_SIZE));
corners[7].setPosition(new Vector3(DEFAULT_SIZE,-DEFAULT_SIZE,-DEFAULT_SIZE));
corners[0].addLink(corners[1]);
corners[1].addLink(corners[2]);
corners[2].addLink(corners[3]);
corners[3].addLink(corners[0]);
corners[0].addLink(corners[4]);
corners[1].addLink(corners[5]);
corners[2].addLink(corners[6]);
corners[3].addLink(corners[7]);
corners[4].addLink(corners[5]);
corners[5].addLink(corners[6]);
corners[6].addLink(corners[7]);
corners[7].addLink(corners[4]);
// corners[0].addLink(corners[5]);
// corners[1].addLink(corners[6]);
// corners[2].addLink(corners[7]);
// corners[3].addLink(corners[4]);
//
// corners[0].addLink(corners[2]);
// corners[5].addLink(corners[7]);
setPosition(new Vector3());
}
public Cube(String n)
{
setName(n);
//assign Id
number = cubeCount;
//Increment cube counter
cubeCount++;
}
////////////////////////////////////////////////////////////////
//Setters
////////////////////////////////////////////////////////////////
private void setName(String n)
{
name = n;
}
private void setPosition(Vector3 v)
{
position = v;
}
public Vertex[] getVerts()
{
return corners;
}
public void printCubeVertices()
{
for(int i = 0; i < 8; i++)
System.out.println(corners[i].getPosition().toString());
}
public void rotateZ(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempZ = corners[i].getPosition().z();
double newX = (corners[i].getPosition().x()-position.x())*Math.cos(degrees)-(corners[i].getPosition().y()-position.y())*Math.sin(degrees);
double newY = (corners[i].getPosition().x()-position.x())*Math.sin(degrees)+(corners[i].getPosition().y()-position.y())*Math.cos(degrees);
corners[i].setPosition(new Vector3(newX,newY,tempZ));
}
}
public void rotateX(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempX = corners[i].getPosition().x();
double newZ = (corners[i].getPosition().z()-position.z())*Math.cos(degrees)-(corners[i].getPosition().y()-position.y())*Math.sin(degrees);
double newY = (corners[i].getPosition().z()-position.z())*Math.sin(degrees)+(corners[i].getPosition().y()-position.y())*Math.cos(degrees);
corners[i].setPosition(new Vector3(tempX,newY,newZ));
}
}
public void rotateY(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempY = corners[i].getPosition().y();
double newZ = (corners[i].getPosition().z()-position.z())*Math.cos(degrees)-(corners[i].getPosition().x()-position.x())*Math.sin(degrees);
double newX = (corners[i].getPosition().z()-position.z())*Math.sin(degrees)+(corners[i].getPosition().x()-position.x())*Math.cos(degrees);
corners[i].setPosition(new Vector3(newX,tempY,newZ));
}
}
}
Vertex.java
package com.my3d.graphics;
import java.util.ArrayList;
public class Vertex
{
private Vector3 position;
private ArrayList<Vertex> linked = new ArrayList<Vertex>();
public Vertex()
{
setPosition(new Vector3(0.0,0.0,0.0));
}
public Vertex(Vector3 v)
{
setPosition(v);
}
public void setPosition(Vector3 pos)
{
position = pos;
}
public void addLink(Vertex v)
{
linked.add(v);
}
public void removeLink(Vertex v)
{
linked.remove(v);
}
/////////////////////////////////////////////////////////////////
//Getters
/////////////////////////////////////////////////////////////////
public Vector3 getPosition()
{
return position;
}
public ArrayList<Vertex> getLinks()
{
return linked;
}
}
Vector3.java
package com.my3d.graphics;
public class Vector3
{
private double[] xyz;
// public double x;
// public double y;
// public double z;
public Vector3()
{
xyz = new double []{0.0,0.0,0.0};
// x = xyz[0];
// y = xyz[1];
// z = xyz[2];
}
public Vector3(double x,double y,double z)
{
xyz = new double []{x,y,z};
// xyz[0] = x;
// xyz[1] = y;
// xyz[2] = z;
}
public Vector3(double[] array)
{
try
{
if(array.length > 3)
{
RuntimeException e = new RuntimeException("The Vector3 is too big by (" +(array.length - 3) +") elements you muppet.\nremember that a Vector3 supports arrays with a length of 3. You have (" +array.length +")");
throw e;
}
if(array.length < 3)
{
RuntimeException e = new RuntimeException("The Vector3 is too small by (" +(3 - array.length) +") elements you muppet.\nremember that a Vector3 supports arrays with a length of 3. You have (" +array.length +")");
throw e;
}
xyz[0] = array[0];
xyz[1] = array[1];
xyz[2] = array[2];
}
catch(RuntimeException e)
{
System.out.println(e);
}
}
///////////////////////////////////////////////////////////////////////////
public void x(double a)
{
xyz[0] = a;
}
public void y(double a)
{
xyz[1] = a;
}
public void z(double a)
{
xyz[2] = a;
}
public double x()
{
return xyz[0];
}
public double y()
{
return xyz[1];
}
public double z()
{
return xyz[2];
}
public void normalize()
{
double length = Math.sqrt((xyz[0] * xyz[0]) + (xyz[1] * xyz[1]) + (xyz[2] * xyz[2]));
xyz[0] = xyz[0]/length;
xyz[1] = xyz[1]/length;
xyz[2] = xyz[2]/length;
}
public String toString()
{
return "(" + xyz[0] + "," + xyz[1] + "," + xyz[2] + ")";
}
}
The best I can do is trace it to public double x(){return xyz[0];}
in the Vector3 class when the paint method is called in the viewport class but when I try and step through they are never null. I get the impression that the jvm is catching up with itself while trying to reassign new positions to the vertices during the rotation.
Edit: Stacktrace
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at com.my3d.graphics.Vector3.x(Vector3.java:70)
at com.my3d.graphics.Viewport.paint(Viewport.java:107)
at java.awt.Canvas.update(Unknown Source)
at sun.awt.RepaintArea.updateComponent(Unknown Source)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Additional Info:
Manager.java
package com.my3d.core;
import java.awt.Color;
import com.my3d.graphics.Viewport;
import com.my3d.graphics.Wind;
import com.my3d.utils.Props;
public class Manager {
/**
* #param args
*/
public static void main(String[] args)
{
Props.loadPropsFromFile();
Wind wind = new Wind();
wind.setVisible(true);
Viewport view = new Viewport("Main view");
view.setPreferredSize(Props.getWindowSize());
view.setBackground(Color.LIGHT_GRAY);
wind.add(view);
view.setVisible(true);
wind.pack();
view.run();
}
}
Wind.java
package com.my3d.graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import com.my3d.utils.Props;
import javax.swing.JFrame;
public class Wind extends JFrame
{
/**
*
*/
private static final long serialVersionUID = 1L;
private boolean fullScreen = false;
//Constructor
public Wind()
{
super(Props.getDefaultTitle() +Props.getTitleSplash());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fullScreen = Props.getFullScreen();
if (fullScreen)
{
setUndecorated(true);
GraphicsDevice vc;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc= ge.getDefaultScreenDevice();
vc.setFullScreenWindow(this);
}
else
{
setBounds(0,0,Props.getWindowWidth(),Props.getWindowHeight());
setLocationRelativeTo(null);
}
}
public void setTitle(String s)
{
//if(Props.getRandomSplashes())
super.setTitle(Props.getDefaultTitle() +s);
}
public void setRandomTitle()
{
if(Props.getRandomSplashes())
Props.setTitleSplash(Props.randomizeSplash());
super.setTitle(Props.getDefaultTitle() +Props.getTitleSplash());
}
public void setFullScreen(boolean b)
{
fullScreen = b;
}
//this method wont work >>
public void refactorWindow()
{
if (fullScreen)
{
setUndecorated(true);
GraphicsDevice vc;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc= ge.getDefaultScreenDevice();
vc.setFullScreenWindow(this);
}
else
{
setSize(Props.getWindowSize());
setLocationRelativeTo(null);
}
}
}
Props.java
package com.my3d.utils;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class Props
{
private static String gameTitle;
private static String splashTitle;
private static Dimension windowSize;
//private static int gameState;
private static boolean fullScreen;
private static boolean randomSplashes;
private static BufferedImage tileSheet;
///////////////////////////////////////////////////////////////
//Getters
///////////////////////////////////////////////////////////////
public static Dimension getWindowSize()
{
return windowSize;
}
public static int getWindowWidth()
{
return (int) windowSize.getWidth();
}
public static int getWindowHeight()
{
return (int) windowSize.getHeight();
}
public static String getTitleSplash()
{
return splashTitle;
}
public static String getDefaultTitle()
{
return gameTitle;
}
public static boolean getFullScreen()
{
return fullScreen;
}
public static boolean getRandomSplashes()
{
return randomSplashes;
}
public static BufferedImage getTileSheet()
{
return tileSheet;
}
/////////////////////////////////////////////////////////////////////
//Setters
/////////////////////////////////////////////////////////////////////
public static void setWindowSize(int width, int height)
{
windowSize = new Dimension(width,height);
}
public static void setTitleSplash(String s)
{
splashTitle = s;
}
private static void setDefaultTitle(String s)
{
gameTitle = s;
}
////////////////////////////////////////////////////////////////////
//loader
////////////////////////////////////////////////////////////////////
public static void loadPropsFromFile()
{
File file = new File("res\\config\\props.props");
try
{
Scanner input = new Scanner(file);
String lInput = input.nextLine().trim();
setWindowSize(Integer.parseInt(lInput.substring(lInput.indexOf('=')+1,lInput.indexOf(','))),Integer.parseInt(lInput.substring(lInput.indexOf(',')+1,lInput.length())));
lInput = input.nextLine().trim();
if(lInput.substring(lInput.indexOf('=')+1,lInput.length()).equals("true"))
fullScreen = true;
else
fullScreen = false;
lInput = input.nextLine().trim();
if(lInput.substring(lInput.indexOf('=')+1,lInput.length()).equals("true"))
{
lInput = input.nextLine().trim();
setDefaultTitle(lInput.substring(lInput.indexOf('=')+1,lInput.length()));
randomSplashes = true;
setTitleSplash(randomizeSplash());
}
else
{
lInput = input.nextLine().trim();
setDefaultTitle(lInput.substring(lInput.indexOf('=')+1,lInput.length()));
setTitleSplash("");
randomSplashes = false;
}
lInput = input.nextLine().trim();
try
{
tileSheet = ImageIO.read(new File(loadFromDirectory(lInput.substring(lInput.indexOf('=')+1,lInput.length()))));
}
catch (IOException e)
{
System.out.println("Tilesheet Cannot be found.");
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
///////////////////////////////////////////////////////////////////
//Utility
///////////////////////////////////////////////////////////////////
public static String loadFromDirectory(String key)
{
String s = "";
File sFile = new File("res\\config\\dirs.dir");
try
{
Scanner sInput = new Scanner(sFile);
while(sInput.hasNextLine())
{
String st = sInput.nextLine().trim();
if(key.equals(st.substring(1,st.length())));
s = sInput.nextLine().trim();
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
return s;
}
public static String randomizeSplash()
{
if(randomSplashes)
{
File sFile = new File("res\\config\\splash.props");
ArrayList<String> sArray = new ArrayList<String>();
try
{
Scanner sInput = new Scanner(sFile);
while(sInput.hasNextLine())
{
sArray.add(sInput.nextLine().trim());
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
Random rand = new Random();
String s = sArray.get(rand.nextInt(sArray.size()));
splashTitle = s;
return s;
}
else
return "";
}
}
props.props
windowSize =1000,800
startInFullscreen =false
randomSplashes =false
defaultTitle =3D Prototype
tileSheet =tileSheet
dirs.dir
<startGame>
res\\img\\button\\Start_game_text.png
res\\img\\button\\Start_game_active.png
res\\img\\button\\Start_game_click.png
<quitGame>
res\\img\\button\\Quit_game_text.png
res\\img\\button\\Quit_game_active.png
res\\img\\button\\Quit_game_click.png
<menuBackground>
res\\img\\Menu_background.png
<resumeGame>
res\\img\\button\\Resume_game_text.png
res\\img\\button\\Resume_game_active.png
res\\img\\button\\Resume_game_click.png
<tileSheet>
res\\img\\Final_Sub_Hunt.png
Update: mark your field xyz in Vector3 as final. My reading of the JMM says that, without a synchronizer or final, it is legal for the Event Dispatch Thread (EDT), from whence your stack trace comes, to see that non-final field as not yet initialized by the constructor when the constructor is run in a separate (your Viewport.run()) thread. See this SO question or the JMM FAQ.
The correct place to look in the JLS for this is in Section 17.5 (though you really need to read all of 17, and a lot of times, to start to grok this):
An object is considered to be completely initialized when its
constructor finishes. A thread that can only see a reference to an
object after that object has been completely initialized is guaranteed
to see the correctly initialized values for that object's final
fields.
Note that the same guarantee does not hold for non-final fields; if you want safety there, you will need to use synchronization edges of some kind (explicit synchronizers, volatile, whatever.)
Related discussion from Alex Miller, who I think putters around StackOverflow as https://stackoverflow.com/users/7671/alex-miller and might poke his head in if we say hello.) And a related SO question.
Original Answer
Looks like you have an error in your Vector3(double[]) constructor, which never initializes the double[] xyz field. Only place I could find where that field would not be initialized and yield an NPE.
I can't tell from your code how many threads you have running around. If you are creating instances of Vector3 in one thread and placing them into a collection while they are being displayed by a separate rendering thread, it is possible you are observing them in a state of partial construction. Generally things are arranged to make this unlikely (I can't remember off the top of my head what the memory model says about publishing partially-constructed objects, but it can happen if you let this slip out of the constructor; I don't see that happening here.)
JA
catch(RuntimeException e)
{
System.out.println(e);
}
This segment allows you to create a Vector3 without properly initialising the array. That's a potiential problem.
Both of the non-default constructors make no attempt to check x, y or z is null before assigning them.
I would start there, then consider unit tests.