I have a custom ImageTextButton in which I render the button to a FrameBuffer first and then draw with frameBuffer.getColorBufferTexture(). I don't really want to do this but I use a custom shader with this button that creates some visual effects and the only way I have been able to achieve it is with a FrameBuffer. I was surprised to find this actually works very smooth and fast though, the whole process takes 1-2ms on slow devices and having several instances doesn't cause any kind of framerate drop, so I am happy with this bit.
The issue I am having though is when I enable clipping on the ImageTextButton (with setClip(true)). The reason for this is the button can change in width, and I would like it to clip the text within the bounds of the button. If I disable the FrameBuffer and render normally, this part also works very well. If I combine the 2, it seems the clipping process gets confused and the result is either no text or very small parts of the text.
So here is the relevant code. I assumed it was because I set the FrameBuffer and SpriteBatch size/projection matrix just to deal with the active area (for efficiency) however if I don't modify any of this and use the same batch/projection matrix, so the FrameBuffer manages the whole screen, it is still the same result.
public void initFrameBuffer(){
xCache = (int) super.getX(); yCache = (int) super.getY();
widthCache = (int) super.getWidth(); heightCache = (int) super.getHeight();
frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, widthCache, heightCache, false);
fboProjectionMatrix.setToOrtho2D(xCache, yCache+heightCache, widthCache, -heightCache);
this.fbBatch = new SpriteBatch();
this.fbBatch.setProjectionMatrix(fboProjectionMatrix);
this.frameBufferReady = true;
}
public void doFrameBuffer(Batch batch, float parentAlpha){
batch.end();
frameBuffer.begin();
fbBatch.begin();
Gdx.gl20.glClearColor(0f, 0.0f, 0.0f, 0.0f);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
super.draw(fbBatch, parentAlpha);
fbBatch.end();
frameBuffer.end();
batch.begin();
}
public void drawFrameBufferObject(Batch batch, float parentAlpha){
batchColorCache = batch.getColor();
batch.setColor(1.0f, 1.0f, 1.0f, parentAlpha);
batch.draw(frameBuffer.getColorBufferTexture(), getX(), getY());
batch.setColor(batchColorCache);
}
#Override
public void draw(Batch batch, float parentAlpha) {
if (!this.frameBufferReady) initFrameBuffer();
doFrameBuffer(batch, parentAlpha);
drawFrameBufferObject(batch, parentAlpha);
}
Sorry for the long code, it's actually heavily trimmed down for the necessary parts..
Help hugely appreciated as always!
After much playing, the solution I have found is one that could probably be useful in other situations, and that is true clipping of the BitmapFontCache by vertex modification, no scissors involved! So if anyone would find this useful, the code is;
float xStart = ...start position of clip
float xEnd = ...end position of clip
//vertex offset numbers
int x_1 = 0, x_2 = 5, x2_1 = 10, x2_2 = 15;
int u_1 = 3, u_2 = 8, u2_1 = 13, u2_2 = 18;
for (int j = 0, n = pageCount; j < n; j++) {
int c = cache.getVertexCount(j);
int newIdx = 0;
if (c > 0) { // ignore if this texture has no glyphs
float[] vertices = cache.getVertices(j);
for(int i = 0; i < vertices.length; i+=20){
//if any of the vertices are outside the label, don't put them in the new cache
if(vertices[i+x2_1] > xStart && vertices[i+x_1] < xEnd){
for(int k = 0; k < 20; k++){
clippedVerts[j][newIdx+k] = vertices[i+k];
}
//case on major left glyph
if(vertices[i+x_1] < xStart){
float xDiff = vertices[i+x2_1]-xStart; //difference between right of glyph and clip
float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
float uDiff = vertices[i+u2_1] - vertices[i+u_1];
float newU = vertices[i+u2_1] - uDiff*xRatio;
clippedVerts[j][newIdx+x_1] = xStart;
clippedVerts[j][newIdx+x_2] = xStart;
clippedVerts[j][newIdx+u_1] = newU;
clippedVerts[j][newIdx+u_2] = newU;
}
//case on major right glyph
if(vertices[i+x2_1] > xEnd){
float xDiff = xEnd-vertices[i+x_1]; //difference between left of glyph and clip
float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
float uDiff = vertices[i+u2_1] - vertices[i+u_1];
float newU_2 = vertices[i+u_1] + uDiff*xRatio;
clippedVerts[j][newIdx+x2_1] = xEnd;
clippedVerts[j][newIdx+x2_2] = xEnd;
clippedVerts[j][newIdx+u2_1] = newU_2;
clippedVerts[j][newIdx+u2_2] = newU_2;
}
newIdx += 20;
}
}
}
clippedIdx[j] = newIdx;
}
for (int j = 0, n = pageCount; j < n; j++) {
int idx = clippedIdx[j];
if (idx > 0) { // ignore if this texture has no glyphs
float[] vertices = clippedVerts[j];
batch.draw(regions.get(j).getTexture(), vertices, 0, idx);
}
}
Related
There is a comment on here, on a stackflow post that answers the question of how to achieve super fast median calculation on opencv. The question is here:
super fast median of matrix in opencv (as fast as matlab)
The problem is that the code is on C/C++ and I can't find a way to convert this to opencv for java or C#, since I'm trying to port this to OpenCV for Xamarin.Forms
Anyone knows how to conver this?
double medianMat(cv::Mat Input, int nVals) {
// COMPUTE HISTOGRAM OF SINGLE CHANNEL MATRIX
float range[] = {
0,
nVals
};
const float * histRange = {
range
};
bool uniform = true;
bool accumulate = false;
cv::Mat hist;
calcHist( & Input, 1, 0, cv::Mat(), hist, 1, & nVals, & histRange, uniform, accumulate);
// COMPUTE CUMULATIVE DISTRIBUTION FUNCTION (CDF)
cv::Mat cdf;
hist.copyTo(cdf);
for (int i = 1; i <= nVals - 1; i++) {
cdf.at < float > (i) += cdf.at < float > (i - 1);
}
cdf /= Input.total();
// COMPUTE MEDIAN
double medianVal;
for (int i = 0; i <= nVals - 1; i++) {
if (cdf.at < float > (i) >= 0.5) {
medianVal = i;
break;
}
}
return medianVal / nVals;
}
UPDATE:
As an example, this is my tried method on c# to calculate the mediam of a grey scale Mat. It fails, can you see what I did wrong?
private static int Median2(Mat Input)
{
// COMPUTE HISTOGRAM OF SINGLE CHANNEL MATRIX
MatOfInt mHistSize = new MatOfInt(256);
MatOfFloat mRanges = new MatOfFloat(0f, 256f);
MatOfInt channel = new MatOfInt(0); // only 1 channel for grey
Mat temp = new Mat();
Mat hist = new Mat();
Imgproc.CalcHist(Java.Util.Arrays.AsList(Input).Cast<Mat>().ToList(), channel, temp, hist, mHistSize, mRanges);
float[] cdf = new float[(int)hist.Total()];
hist.Get(0, 0, cdf);
for (int i = 1; i < 256; i++)
{
cdf[i] += cdf[i - 1];
cdf[i - 1] = cdf[i - 1] / Input.Total();
}
// COMPUTE CUMULATIVE DISTRIBUTION FUNCTION (CDF)
float total = (float)Input.Total();
// COMPUTE MEDIAN
double medianVal=0;
for (int i = 0; i < 256; i++)
{
if (cdf[i] >= 0.5) {
medianVal = i;
break;
}
}
return (int)(medianVal / 256);
}
I want to extract circles in and Image, So I extract them with below code:
Mat circles = new Mat();
Imgproc.HoughCircles(adaptiveThresh, circles, Imgproc.HOUGH_GRADIENT, 1.0, (double) adaptiveThresh.rows() / 40, 100.0, 30.0, 20, 30);
And then I iterate through them with below code:
for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
int radius = (int) Math.round(c[2]);
Imgproc.circle(source, center, radius, new Scalar(0, 0, 255), 3);
}
But I want to sort them from topleft to bottom right, And the problem is I can not access the x and y of the circles!
How may I sort them based on row from top left to bottom right?
Your question is can be confusing. As it can be (1) sort distance to the circle to the top left of the image. (2)sort the distance from top left of each circle to top left of the image corner?
I assume you want to find the circle which is most close to top left case (1).
Here is my response.
From the C++ sample( i guess you are using android, not very familiar). You can convert using my sample code below.
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// circle center
circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
// circle outline
circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
the center should be the point you want.
To sort them from top left to bottom right using city block distance you just have to
void sort_points (std::vector<Vec3f> &array)
{
std::cout<<"Elements in the array: "<<array.size()<<std::endl;
//comparisons will be done n times
for (int i = 0; i < array.size(); i++)
{
//compare elemet to the next element, and swap if condition is true
for(int j = 0; j < array.size() - 1; j++)
{
if ((array[j][0]+array[j][1]) > (array[j+1][0]+ array[j+1][1])
Swap(&array[j], &array[j+1]);
}
}
}
int main(argc,argv)
// ...................//do your stuff
vector<Vec3f> circles; //detected circle
// ... //do the detection
sort_points(circles);
//circles here is fully sorted from city block distance
std::cout<<circles<<std::endl; // print out all sorted circile
// ...................//do your stuff
}
if it is 2nd case just change if by
if ((array[j][0]+array[j][1]-2*array[j][2]) > (array[j+1][0]+ array[j+1][1]-2*array[j+1][2])
Here is what you need to do:
First, declare a Circle class (for encapsulation the circle properties for sorting purpose)
class Circle {
int cX;
int cY;
int radius;
double distance;
}
Now iterate over the HoughCircles result, and create a Circle instance and then add it to the List
List<Circle> circleList = new ArrayList<>();
//start point, it is used to calculate the distance
Point p1 = new Point(0, 0);
for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
int radius = (int) Math.round(c[2]);
Imgproc.circle(source, center, radius, new Scalar(0, 0, 255), 3);
// here create the Circle instance
Circle circle = new Circle();
cricle.cX = center.x;
circle.cY = center.y;
circle.radius= radius;
double D = Math.sqrt(Math.pow(abs(p1.x - circles.x), 2) + Math.pow(abs(p1.y - circles.y), 2));
circle.distance = D;
// add the circle instance to the list
circleList.add(circle);
}
Now sort the circles in the list, use distance from small to bigger
circleList.sort(new Comparator<File>() {
#Override
public int compare(Circle c1, Circle c2) {
return Double.compare(c1.distance, c2.distance);
}
});
Now you can do what you want with circles list.
Hope it helps!!
Im trying to build a simple Music Visualisation App which just should resize a Circle. So if the Music Part which is currently playing is loud it should get bigger and if not it should get smaller.
To Visualize the Circle I just created a custom View Class which draws the circle in the onDraw Method.
To get the informations out of the current Audio, I found the Visualizer Class of Android and also used the setDataCaptureListener.
mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
mVisualizer.setDataCaptureListener(
new Visualizer.OnDataCaptureListener() {
public void onWaveFormDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
mVisualizerView.updateVisualizer(bytes);
}
public void onFftDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
}
}, (int)(Visualizer.getMaxCaptureRate() / 1.5), true, false);
But my Problem is that I don't really know how I use the byte Array which is given back, to find out the music change in general (got louder or not ?).
I just tried to get the average of the array but this gives me completely bad results. The circle changed his size like it is on drugs. So I thought maybe the array has too many outlined/extreme values (which was true) so I calculated the median of the array. This gaved me better results but still isn't what I want. It's not very smooth and it's to complex. I always have to sort the array which is not really efficient. What am I thinking wrong ?
Im really a beginner in this AudioFX section and Im completely sorry If this is a dumb question and attempt of me.
Thank you for your help !
EDIT:
private float schwelle = 5000;
private float last = 0;
...
float summe = 0;
for (Byte currentByte: mBytes)
summe += currentByte;
if (summe > schwelle && summe > last)
{
last = summe; //make it bigger
}
else {
last -= 100; //make circle smaller
}
canvas.drawCircle(getWidth()/2,getHeight()/2,last / 100,mForePaint);
A really good git project is https://github.com/felixpalmer/android-visualizer.
I myself came up with this:(it's a lot simple than the git solution)
You can use the values of the array to draw the the waveform on the outline of a circle using trigonometry, and make the start radius of the circle bigger if the sum of the array is bigger than certain treshhold:
class StarWaveformRenderer implements Renderer {
private Paint p = new Paint();
private static final int BOOST_TRASH_HOLD = 10000;
private float stretchFade = 1; //circle fades after a prominent beat
#Override
public void render(Canvas canvas, byte[] data) {
if (data == null || data.length == 0)
return;
int centerX = canvas.getWidth() / 2;
int centerY = canvas.getHeight() / 2;
float stretch = stretchFade;
int sum = RenderUtils.sum(data);
p.setColor((p.getColor() + sum / 2)); //change color of circle
if (sum > BOOST_TRASH_HOLD) {//prominent beat
stretch = (float) Math.min(canvas.getWidth(), canvas.getHeight()) / Byte.MAX_VALUE / 3; //maximum
stretchFade = stretch;
}
double radDif = 2 * Math.PI / data.length; //the angle between each element of the array
double radPos = 0;
float lX = (float) Math.cos(radPos) * data[0] + centerX;
float lY = (float) Math.sin(radPos) * data[0] + centerY;
float cX;
float cY;
for (byte b : data) {
cX = (float) Math.cos(radPos) * b * stretch + centerX;
cY = (float) Math.sin(radPos) * b * stretch + centerY;//calculate position of outline, stretch indicates promince of the beat
canvas.drawLine(lX, lY, cX, cY, p);
lX = cX;
lY = cY;
radPos += radDif;
}
stretchFade = Math.max(1, stretchFade / 1.2f);//beat fades out
}
}
You can programm your own renderes and let the user select which one he wants to use. Just pass the array from onWaveformDataCapture to the onRender method.
Utils for analysing the waveform (the amplitude is stored kind of weird):
class RenderUtils {
private static final byte SHIFT = Byte.MAX_VALUE;
static int sum(byte[] data) {
int sum = 0;
for (byte b : data)
sum += b;
return sum;
}
static int toAmplitude(byte b) {
return b > 0 ? b + SHIFT : -b;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
static float toAmplitude(float f) {
return f > 0 ? f + SHIFT : -f;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
}
How can I draw Path with fading (opacity or thicknes) line? Something like this.
I know there is LinearGradient shader for Paint, but it won't bend along the Path.
One possible solution might be to get points along the Path and just draw it by myself through the segments`. But I coouldn't find any method for that either.
I came up with the following code. The mos important thing is PathMeasure's getPosTan() method.
if (getGesturePath() != null) {
final short steps = 150;
final byte stepDistance = 5;
final byte maxTrailRadius = 15;
pathMeasure.setPath(getGesturePath(), false);
final float pathLength = pathMeasure.getLength();
for (short i = 1; i <= steps; i++) {
final float distance = pathLength - i * stepDistance;
if (distance >= 0) {
final float trailRadius = maxTrailRadius * (1 - (float) i / steps);
pathMeasure.getPosTan(distance, pathPos, null);
final float x = pathPos[0] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
final float y = pathPos[1] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
paint.setShader(new RadialGradient(
x,
y,
trailRadius > 0 ? trailRadius : Float.MIN_VALUE,
ColorUtils.setAlphaComponent(Color.GREEN, random.nextInt(0xff)),
Color.TRANSPARENT,
Shader.TileMode.CLAMP
));
canvas.drawCircle(x, y, trailRadius, paint);
}
}
}
I've a custom view which draws a running graph - some magnitude versus time. Now I want to implement a custom scroll bar for this so that I can view past data which are offscreen. The data is available to me. I just need the %offset selection by the user.
Any help/suggestions on implementation would be v helpful.
Code Snippet from my Custom view's onDraw method
public void onDraw(Canvas canvas) {
int totalpts = data.size();
scale = getWidth() / (float) maxpoints;
List<Data> display = new ArrayList<Data>();
int initial = 1;
if (totalpts > maxpoints) {
initial = totalpts - maxpoints;
display = data.subList(initial, data.size() - 1);
} else {
display = data;
}
int size = display.size();
Data start = null;
float x1 = 0, x2 = 0, x = 0;
if (size > 1) {
x1 = getWidth();
start = display.get(display.size() - 1);
for (int i = display.size() - 1; i >= 0; i--) {
Data stop = display.get(i);
x = x1;
x1 -= (stop.x * scale / 1000);
canvas.drawLine(x, start.Y, x1, stop.Y, paint1);
start = stop;
}
}
}
Try putting your custom control inside a HorizonatalScrollView (assuming you want it to scroll horizontally), use ScrollView otherwise), setting the width of your control to "WRAP_CONTENT", and the HoizontalScrollView to "FILL_PARENT". Without seeing the code for your custom view, it's difficult to know whether you might need to do some tinkering with the width calculation to get this working.