Given a StyledText, initializied with SWT.MULTI and SWT.WRAP, I need to calculate the appropriate height hint based on the appropriate lines count for the content.
In this image you can see how the editor is resized when the content changes
The code that calculates the lines to display is the following
private int calcRealLinesCount() {
final int componentWidth = styledText.getSize().x;
final int dumbLinesCount = styledText.getLineCount();
int realLinesCount = 0;
for (int i = 0; i < dumbLinesCount; i++) {
final String lineText = styledText.getLine(i);
final Point lineTextExtent = styledTextGc.textExtent(lineText);
final double lines = lineTextExtent.x / (double) componentWidth;
realLinesCount += (int) Math.max(1D, Math.ceil(lines));
}
return Math.max(dumbLinesCount, realLinesCount);
}
The lines count is then used to get the appropriate height
((GridData) layoutData).heightHint = realLinesCount * fontHeight;
However, this code does not consider word wraps, and I cannot think of a way to do it.
Any ideas? Could I do this in a different way?
Thanks to Greg for the JFaceTextUtil#computeLineHeight hint.
I could not use that directly, but I've at least learned how JFace does what I need.
The following is what I'm using now to get a StyledText's line height:
private int computeRealLineHeight(final int lineIndex) {
final int startOffset = styledText.getOffsetAtLine(lineIndex);
final String lineText = styledText.getLine(lineIndex);
if (lineText.isEmpty()) {
return styledText.getLineHeight(startOffset);
}
final int endOffset = startOffset + lineText.length() - 1;
final Rectangle textBounds = styledText.getTextBounds(startOffset, endOffset);
return textBounds.height;
}
Related
I've had a lot of experience with Java, and have already written my own utilities for processing imagery. I realise that there are many tutorials out there for Python, but I'm much more inclined to use Java as I would like to integrate some of the facilities of OpenCV 4.1 with my existing Java code.
One of my experiments is to try and obtain the masks of objects identified ion images using the coco dataset
// Give the textGraph and weight files for the model
private static final String TEXT_GRAPH = "models/mask_rcnn_inception_v2_coco_2018_01_28.pbtxt";
private static final String MODEL_WEIGHTS = "models/mask_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb";
private static final String CLASSES_FILE = "models/mscoco_labels.names";
TextFileConsumer consumer = new TextFileConsumer();
File textFile = new File(CLASSES_FILE);
if (textFile.exists()){
BufferedReader fr1 = new BufferedReader(new FileReader(textFile));
fr1.lines().forEach(consumer::storeLines);
}
String[] CLASSES = consumer.lines.toArray(new String[0]);
Mat image = Imgcodecs.imread("images/bird.jpg");
Size size = image.size();
int cols = image.cols();
int rows = image.rows();
double h = size.height;
double w = size.width;
int hh = (int)size.height;
int ww = (int)size.width;
Mat blob = org.opencv.dnn.Dnn.blobFromImage(image, 1.0, new Size(w, h), Scalar.all(0), true, false);
// Load the network
Net net = org.opencv.dnn.Dnn.readNetFromTensorflow(MODEL_WEIGHTS, TEXT_GRAPH);
net.setPreferableBackend(org.opencv.dnn.Dnn.DNN_BACKEND_OPENCV);
net.setPreferableTarget(org.opencv.dnn.Dnn.DNN_TARGET_CPU);
net.setInput(blob);
ArrayList<String> outputlayers = new ArrayList<String>();
ArrayList<Mat> outputMats = new ArrayList<Mat>();
outputlayers.add("detection_out_final");
outputlayers.add("detection_masks");
net.forward(outputMats,outputlayers);
Mat numClasses = outputMats.get(0);
numClasses = numClasses.reshape(1, (int)numClasses.total() / 7);
for (int i = 0; i < numClasses.rows(); ++i) {
double confidence = numClasses.get(i, 2)[0];
//System.out.println(confidence);
if (confidence > 0.2) {
int classId = (int) numClasses.get(i, 1)[0];
String label = CLASSES[classId] + ": " + confidence;
System.out.println(label);
int left = (int)(numClasses.get(i, 3)[0] * cols);
int top = (int)(numClasses.get(i, 4)[0] * rows);
int right = (int)(numClasses.get(i, 5)[0] * cols);
int bottom = (int)(numClasses.get(i, 6)[0] * rows);
System.out.println(left + " " + top + " " + right + " " + bottom);
}
}
Mat numMasks = outputMats.get(1);
So far so good. My problem now is trying to obtain the segmentation shapes from numMasks Mat.
The size gives 90x100, total 2025000 and type -1*-1*CV_32FC1, isCont=true, isSubmat=true.
I know I have to reshape too, perhaps like this:
numMasks = numMasks.reshape(1, (int)numMasks.total() / 90);
Any help would be greatly appreciated.
Further edit
I think the masks are 15 x 15.
90 x 100 x 15 x 15 gives 2025000 so that gives me the total I saw earlier so I need to resize a mask and mix with original image.
I am writing ESRI geometry compression algorithm in JAVA, I am using this link.
Using instructions mentioned in provided link, this is my current code:
public static void main(String[] args) {
String dirPoints = "-118.356654545455,34.1146;-118.356436363636,34.1143272727273;-118.356418181818,34.1142363636364;-118.356490909091,34.1137636363636";
String compressedGeometry = "";
double xPointPrev = 0.0;
double yPointPrev = 0.0;
int coefficient = 55000;
String coefficient_32 = Integer.toString(coefficient, 32);
compressedGeometry = coefficient_32 + compressedGeometry;
String[] path_XY = dirPoints.split(";");
for (int i = 0, leni = path_XY.length; i < leni; i++) {
String[] xy = path_XY[i].split(",");
double pointX = Double.parseDouble(xy[0].trim());
double pointY = Double.parseDouble(xy[1].trim());
int xDifference = (int) Math.round(coefficient * (pointX - xPointPrev));
int yDifference = (int) Math.round(coefficient * (pointY - yPointPrev));
String xDifference_32 = Integer.toString(xDifference, 32);
compressedGeometry += xDifference_32;
String yDifference_32 = Integer.toString(yDifference, 32);
compressedGeometry += yDifference_32;
xPointPrev = pointX;
yPointPrev = pointY;
}
System.out.println(compressedGeometry);
}
Expected output: "+1lmo-66l1f+1p8af+c-f+1-5-4-q"
But I am getting this: "1lmo-66l1g1p8afc-f1-5-4-q"
What am I missing? Any help is highly appreciated.
Thanks,
According to Integer.toString()
"If the first argument is not negative, no sign character appears in the result."
Therefore I think you need to add the "+".
(And I think the f-g there is their typo).
The code below uses batik-svggen to generate SVG files; the goal is an oh-so-classical goal, but seemingly impossible to get right the first time... Correctly calculate the height and width of some rendered text so that it can be surrounded by a shape -- here, a rectangle.
The class which is used is as follows:
public final class SvgParseNode
{
private static final int LEFTMARGIN = 5;
private static final int TOPMARGIN = 5;
private static final int RIGHTMARGIN = 5;
private static final int BOTTOMARGIN = 5;
private final Graphics2D graphics;
private final String text;
private final int xStart;
private final int yStart;
private final int rwidth;
private final int rheight;
private final int textXOffset;
private final int textYOffset;
#SuppressWarnings("ObjectToString")
public SvgParseNode(final Graphics2D graphics, final ParseNode node,
final int xStart, final int yStart)
{
this.xStart = xStart;
this.yStart = yStart;
this.graphics = Objects.requireNonNull(graphics);
text = Objects.requireNonNull(node).toString();
/*
* First, get the bounds of the text
*/
final Font font = graphics.getFont();
final FontMetrics metrics = graphics.getFontMetrics(font);
final Rectangle2D bounds = metrics.getStringBounds(text, graphics);
final int boundsHeight = (int) bounds.getHeight();
/*
* The width of the target rectangle is that of the bounds width, plus
* both left and right margin on the x axis
*/
// PROBLEM HERE
//rwidth = (int) bounds.getWidth() + LEFTMARGIN + RIGHTMARGIN;
rwidth = metrics.stringWidth(text) + LEFTMARGIN + RIGHTMARGIN;
/*
* The height is that of the bounds height, plus both up and down
* margins on the y avis
*/
rheight = boundsHeight + TOPMARGIN + BOTTOMARGIN;
/*
* The x offset of the text is that of the left x margin
*/
textXOffset = LEFTMARGIN;
/*
* The y offset is that of half of half the bounds height plus the y
* up margin
*/
textYOffset = rheight / 2 + TOPMARGIN;
}
public void render()
{
final Shape rect = new Rectangle(xStart, yStart, rwidth, rheight);
graphics.draw(rect);
graphics.drawString(text, xStart + textXOffset, yStart + textYOffset);
}
public Point getTopAttachPoint()
{
return new Point(xStart + rwidth / 2, yStart);
}
public Point getDownAttachPoint()
{
return new Point(xStart + rwidth / 2, yStart + rheight);
}
}
At the line marked PROBLEM HERE above, I tried to use the width given by .getStringBounds() as the width of the text. Fat luck, it doesn't return the correct result... But the .stringWidth() works.
I use this to write the rendering (code adapted from here):
public final class SvgWriting
{
private static final int XSTART = 10;
private static final int YSTART = 10;
private static final Path OUTFILE;
static {
final String s = System.getProperty("java.io.tmpdir");
if (s == null)
throw new ExceptionInInitializerError("java.io.tmpdir not defined");
OUTFILE = Paths.get(s, "t2.svg");
}
private SvgWriting()
{
throw new Error("no instantiation is permitted");
}
public static void main(final String... args)
throws IOException
{
// Get a DOMImplementation.
final DOMImplementation domImpl =
GenericDOMImplementation.getDOMImplementation();
// Create an instance of org.w3c.dom.Document.
final String svgNS = "http://www.w3.org/2000/svg";
final Document document = domImpl.createDocument(svgNS, "svg", null);
// Create an instance of the SVG Generator.
final SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
final SvgParseNode node = new SvgParseNode(svgGenerator,
new DummyNode(), XSTART, YSTART);
node.render();
try (
final Writer out = Files.newBufferedWriter(OUTFILE);
) {
svgGenerator.stream(out, false);
}
}
private static final class DummyNode
extends ParseNode
{
private DummyNode()
{
super("foo", Collections.emptyList());
}
#Override
public String toString()
{
return "I suck at graphics, I said!";
}
}
}
What I have noticed is that the longer the text, the "more incorrect" .getStringBounds() seems to be; this makes me say that somehow it does not account for the space between each rendered glyph, but that is just a hypothesis.
In the example above, metrics.getStringBounds(...).getWidth() returns ~105, and metrics.stringWidth() return 111.
Anyway, it is really strange; why doesn't .getStringBounds() return a "correct" width? What am I missing?
I'm using LeadingSpanMargin2 as suggested to style my TextView so that it would flow around an Image, the problem is the text I set is dynamic and can be rtl or ltr.
I tried anything I could think of to fix the leadingMargin so that it would be either right or left in both cases, to no avail.
Also the I've tried FlowTextView widget posted in github and it doesn't work well in rtl cases, so please do not suggest that.
Many thanks.
Here's the code I use, which was suggested in another answer:
public void setFlowText(CharSequence text, int width , int height, TextView messageView)
{
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int)Math.ceil(height / textLineHeight);
//For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
SpannableString ss = new SpannableString(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//ss.setSpan(new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageView.setText(ss);
// Align the text with the image by removing the rule that the text is to the right of the image
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
int[]rules = params.getRules();
rules[RelativeLayout.RIGHT_OF] = 0;
}
public class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
private int margin;
private int lines;
private boolean wasDrawCalled = false;
private int drawLineCount = 0;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
boolean isFirstMargin = first;
// a different algorithm for api 21+
if (Build.VERSION.SDK_INT >= 21) {
this.drawLineCount = this.wasDrawCalled ? this.drawLineCount + 1 : 0;
this.wasDrawCalled = false;
isFirstMargin = this.drawLineCount <= this.lines;
}
return isFirstMargin ? this.margin : 0;
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
this.wasDrawCalled = true;
}
#Override
public int getLeadingMarginLineCount() {
return this.lines;
}
}
For the some unknown reason you can get left margin by passing:
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
and to get right margin you need to pass:
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
I don't know what is going on there, but this works!
I have a basic wave generator in java but I need something to remove the clicks I get from when the amplitude of a wave changes sharply. Namely when I start/stop playing a wave, especially if I have a beeping tone.
Phrogz's answer on SO gave a really nice and simple function, but I'm not sure I'm implementing it right.
When I first tried to use it, I couldn't get it to work, but then I seem to remember it working very well... I have since fiddled about a lot with my code and now it doesn't seem to be working very well again.
So here's the closest I could get to an SSCCE:
If you play this you will notice that when the filtering is on (filter = true) the wave is much quieter and the clicks slightly less, but this seems mainly due to the decrease in volume. There is still a noticeable "hit" on each beep, that I don't want, and I don't remember being there before...
import javax.sound.sampled.*;
public class Oscillator{
private static int SAMPLE_RATE = 22050;
private static short MAX_AMPLITUDE = Short.MAX_VALUE;
private static AudioFormat af = null;
private static SourceDataLine line = null;
private int frequency = 440; //Hz
private int numLoops = 1000;
private int beep = 100;
// set to true to apply low-pass filter
private boolean filter = true;
// set the amount of "smoothing" here
private int smoothing = 100;
private double oldValue;
public Oscillator(){
prepareLine();
}
public static void main(String[] args) {
System.out.println("Playing oscillator");
Oscillator osc = new Oscillator();
osc.play();
}
private void prepareLine(){
af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLE_RATE, 16, 2, 4, SAMPLE_RATE, false);
try {
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
if (!AudioSystem.isLineSupported(info)) {
System.out.println("Line does not support: " + af);
System.exit(0);
}
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(af);
}
catch (Exception e) {
System.out.println(e.getMessage());
System.exit(0);
}
}
private void play() {
System.out.println("play");
int maxSize = (int) Math.round( (SAMPLE_RATE * af.getFrameSize())/ frequency );
byte[] samples = new byte[maxSize];
line.start();
double volume = 1;
int count = 0;
for (int i = 0; i < numLoops; i ++){
if (count == beep) {
if(volume==1) volume = 0;
else volume = 1;
count = 0;
}
count ++;
playWave(frequency, volume, samples);
}
line.drain();
line.stop();
line.close();
System.exit(0);
}
private void playWave(int frequency, double volLevel, byte[] samples) {
double amplitude = volLevel * MAX_AMPLITUDE;
int numSamplesInWave = (int) Math.round( ((double) SAMPLE_RATE)/frequency );
int index = 0;
for (int i = 0; i < numSamplesInWave; i++) {
double theta = (double)i/numSamplesInWave;
double wave = getWave(theta);
int sample = (int) (wave * amplitude);
if (filter) sample = applyLowPassFilter(sample);
// left sample
samples[index + 0] = (byte) (sample & 0xFF);
samples[index + 1] = (byte) ((sample >> 8) & 0xFF);
// right sample
samples[index + 2] = (byte) (sample & 0xFF);
samples[index + 3] = (byte) ((sample >> 8) & 0xFF);
index += 4;
}
int offset = 0;
while (offset < index){
double increment =line.write(samples, offset, index-offset);
offset += increment;
}
}
private double getWave(double theta){
double value = 0;
theta = theta * 2 * Math.PI;
value = getSin(theta);
//value = getSqr(theta);
return value;
}
private double getSin(double theta){
return Math.sin(theta);
}
private int getSqr(double theta){
if (theta <= Math.PI) return 1;
else return 0;
}
// implementation of basic low-pass filter
private int applyLowPassFilter(int sample){
int newValue = sample;
double filteredValue = oldValue + (newValue - oldValue) / smoothing;
oldValue = filteredValue;
return (int) filteredValue;
}
}
The relevant method is at the end. If anyone does test this, please be careful of the volume if you have headphones!
So either:
It is working and I'm just expecting too much of such a simple implementation
I'm doing something wrong, stupid and obvious...
If it's just 1. How should/could I get rid of that harsh beat/hit/click from sudden amplitude changes?
If it's 2. good, should be a v short answer for a too long question.
A low pass filter will not remove clicks from sudden amplitude changes. Instead you need to avoid sudden amplitude changes.
You could use the lowpass filter to filter your amplitude level.
**Pseudo code**
for i = 0 to numSamplesInWave-1 do
begin
theta = i / numSamplesInWave;
wave = getWave(theta);
currentAmplitude = applyLowPassFilter(TargetAmplitude);
Sample[i] = wave * currentAmplitude;
end;
Using a lowpass filter as above is fine for smoothing input values. For example when the user changes a volume control.
In other situations it might be more appropriate to create an envelope of some sort. For example synthesizers commonly use ADSR envelopes to smooth the amplitude changes when a new Voice/Sound starts and stops.