I am trying to draw a graph that has a scrollbar,
the graph uses time for the x-axis and i'd like to have a limited x-axis (1 minute)
so up until 1 minute, the scroll bar's page is the length of the scrollbar,
after that the page should be '60 seconds long' and the scrollbar max should be 'elapsed time' long
when you drag the scrollbar it tracks along, and pulls up the relevant bit of the graph.
the graph should auto-scroll until its dragged away (and auto-scroll once its dragged back to max)
this is what i have so far
is defined at the top of the wndproc
static SCROLLINFO sci = {sizeof(SCROLLINFO),SIF_RANGE|SIF_POS|SIF_PAGE|SIF_TRACKPOS,0,0,0,0,0};
in the WM_HSCROLL event:
GetScrollInfo((HWND)lParam, SB_CTL, &sci);
int pos;
pos = sci.nPos;
switch(LOWORD(wParam))
{
case SB_PAGELEFT:
pos -= sci.nPage;
break;
case SB_LINELEFT:
pos -= (sci.nMax/10);
break;
case SB_PAGERIGHT:
pos += sci.nPage;
break;
case SB_LINERIGHT:
pos += (sci.nMax/10);
break;
case SB_THUMBTRACK:
tsTracking = true;
pos = sci.nTrackPos;
break;
case SB_THUMBPOSITION:
tsTracking = false;
pos = sci.nTrackPos;
break;
}
if (pos < sci.nMin) pos = sci.nMin;
if (pos > sci.nMax) pos = sci.nMax;
if (pos != sci.nPos)
{
sci.fMask = SIF_POS;
sci.nPos = pos;
if(sci.nPos >= (sci.nMax-(sci.nPage*1.2)-1)) //this should detect when the thumb has reached the end of the scrollbar
sci.nPos = sci.nMax;
SetScrollInfo((HWND)lParam, SB_CTL, &sci, TRUE);
PostMessage(hWnd, WM_COMMAND, IDM_DISPLAYRESULTS, 0);
}
break;
in the drawing code called from WM_PAINT
(yes i realize i shouldn't be setting the scrollbar's info here, i intend to put it in its proper place once its working properly)
at the top of the graph-drawing function:
static SCROLLINFO sci = {sizeof(SCROLLINFO),SIF_RANGE|SIF_POS|SIF_PAGE|SIF_TRACKPOS,0,0,0,0,0};
in the graph-drawing function:
__int64
elapsed_time = g_h_maxX-g_h_minX, //the elapsed time
windowLengthMS, //the current window length in ms (between 0 and MAX_TIME_WIDTH_MS)
start, //the start of the current window
end; //the end of the current window
static UINT32 windowMaxS = 1; //how long the window max is (used for x sub-divisions)
double
xTickStep, //x division step
yTickStep, //y division step
xScale, //x-scale (used to get the time relative to the elapsed time and size of the graph)
yScale; //y-scale (used to get the data being plotted relative to its max, and the size of the graph)
if(!tsTracking) //a global bool, checking if the scrollbar's thumb is being dragged
{
GetScrollInfo(get_chwnd(IDCW_HARMONICSRESULTSTIMESHIFTSB),SB_CTL,&sci); //gets the scroll info for the scrollbar
int pos = sci.nPos*(double(elapsed_time)/double(sci.nMax - sci.nMin)); //gets the position relative to the new max
sci.nMin = g_h_minX; //sets min (should always be the same, done for completeness)
sci.nMax = (((g_h_maxX-MAX_TIME_WIDTH_MS)>0)?(g_h_maxX):0); //sets max to either max, or 0 (if max isnt bigger then page length)
sci.nPage = MAX_TIME_WIDTH_MS; //sets the page length to the window length
sci.nPos = pos; //sets position to its new value
if(sci.nPos >= (sci.nMax-(sci.nPage*1.2)-1)) //if position is one "page" from the end
sci.nPos = sci.nMax; //set it to max
SetScrollInfo(get_chwnd(IDCW_HARMONICSRESULTSTIMESHIFTSB),SB_CTL,&sci,true);
} //set scroll info
if (elapsed_time > MAX_TIME_WIDTH_MS) //if elapsed time is longer then the max window length
{
end = ((double)sci.nPos / double(sci.nMax)) * g_h_maxX; //set the end to current (scroll position / scroll max) * latest time -> this should put end relative to the scrollbar
start = end-MAX_TIME_WIDTH_MS; //sets start to end - window length
if (start < 0) //if start is now less than zero
{
end = MAX_TIME_WIDTH_MS; //set end to window length
start = 0; //set start to 0
}
}
else //if (elapsed_time <= MAX_TIME_WIDTH_MS)
{
start = g_h_minX; //start is min
end = g_h_maxX; //end is max
}
windowLengthMS = (end-start); //find the current window length
if(_MSTOSECS(windowLengthMS) > windowMaxS) //if the current window length beats one fo the markers
windowMaxS = ((windowMaxS < 10)?windowMaxS+1:(windowMaxS==40)?windowMaxS*1.5:windowMaxS*2); //change subdiv scaling
xScale = double(graph_width)/double(windowLengthMS); //set x-scale to width / window length
yScale = (double)(graph_height)/((double)g_h_maxY - (double)g_h_minY); //set y-scale to height / (maxVal-minVal)
int ticks = _MSTOSECS(elapsed_time); //ticks = full seconds elapsed
xTickStep = double(graph_width)/double(ticks+1); //tickstep = seconds+1 (accounts for 0)
yTickStep = double(graph_height)/double(Y_TICKS); //6 y-ticks, constant.
SelectObject(hDC,hThickPen);
{ //scope to clear variables!
double x; //x to store subdiv x-location
for(i = ticks; i >= 0; i--) //for each subdiv
{
if(((windowMaxS>40)?(!(i%3)):(windowMaxS >20)?(!(i%2)):i)) //have if <=20 secs, each second is shown on x, >20 <=40, every second second is shown, >40 seconds, every 3rd second is shown
{
x = round((_SECSTOMS(i)*xScale)); //find x-pos
sprintf(buf,"%d",(i+_MSTOSECS(start))); //print to buffer
SetRect(&rc, fr.left+x-5, fr.bottom+5, fr.left+x+xTickStep, fr.bottom+25); //get text rectangle
DrawText(hDC, buf, -1, &rc, DT_SINGLELINE | DT_VCENTER | DT_LEFT); //draw text
}
if (i!=ticks && (windowMaxS>40)?(!(i%6)):(windowMaxS >20)?(!(i%4)):(windowMaxS>10)?(!(i%2)):i)//every <10, each sec gets a subdiv, <20 each second tick gets a subdiv, <40 each 6th tick gets a subdiv
{
MoveToEx(hDC, GRAPH_XL_OFFSET+x, graph_bottom, NULL); //draw the line
LineTo(hDC, GRAPH_XL_OFFSET+x, graph_bottom-graph_height); //draw the line
}
}
}
as for globals:
g_h_minX is the start time and
g_h_maxX is the last result time
MAX_TIME_WIDTH_MS is the "window length" in ms -> how long i want the scroll bar page (60 seconds)
so the idea is to set end to where the scroll bar is, and get the start by taking the window length from end, and working out which part of the graph we're trying to look at.
i've been fiddling with this for the last 2 days and i'm running out of ideas.
im sure i'm close, but i cant quite figure it out,
edit:
updated the code slightly
it also seems i forgot to say what the problem was.
the scolling code isnt working properly, it autoscrolls when new data comes in,
but when i drag the thumb away from the end of the scrollbar, it just snaps to the start, and wont move.
on closer inspection, the arrows work, and the "page-right" "page-left" on the right-click menu work, just not the tracking
any help would be appreciated.
thanks in advance.
I have managed to find the problem,
I ended up converting everything to seconds and working with that, that ended up working, so i must have been having some scaling issues.
the code ended up like this:
case WM_HSCROLL:
{
SCROLLINFO sci = {sizeof(SCROLLINFO),SIF_RANGE|SIF_POS|SIF_PAGE|SIF_TRACKPOS,0,0,0,0,0};
GetScrollInfo((HWND)lParam, SB_CTL, &sci);
int pos;
pos = sci.nPos;
switch(LOWORD(wParam))
{
case SB_PAGELEFT:
pos -= sci.nPage;
break;
case SB_LINELEFT:
pos -= 2;
break;
case SB_PAGERIGHT:
pos += sci.nPage;
break;
case SB_LINERIGHT:
pos += 2;
break;
case SB_THUMBTRACK:
tsTracking = true;
pos = sci.nTrackPos;
break;
case SB_THUMBPOSITION:
tsTracking = false;
pos = sci.nTrackPos;
break;
case SB_LEFT:
pos = sci.nMin;
break;
case SB_RIGHT:
pos = sci.nMax;
break;
}
if (pos < sci.nMin) pos = sci.nMin;
if (pos > sci.nMax) pos = sci.nMax;
if (pos != sci.nPos)
{
sci.fMask = SIF_POS;
sci.nPos = pos;
//if(sci.nPos >= (sci.nMax-sci.nPage)) //this should detect when the thumb has reached the end of the scrollbar
// sci.nPos = sci.nMax;
SetScrollInfo((HWND)lParam, SB_CTL, &sci, TRUE);
PostMessage(hWnd, WM_COMMAND, IDM_DISPLAYRESULTS, 0);
}
}
break;
for the scrolling code and
__int64
elapsed_time = g_h_maxX-g_h_minX, //the elapsed time
windowLengthMS, //the current window length in ms (between 0 and MAX_TIME_WIDTH_MS)
start, //the start of the current window
end; //the end of the current window
static UINT32 windowMaxS = 1; //how long the window max is (used for x sub-divisions)
double
xTickStep, //x division step
yTickStep, //y division step
xScale, //x-scale (used to get the time relative to the elapsed time and size of the graph)
yScale; //y-scale (used to get the data being plotted relative to its max, and the size of the graph)
GetScrollInfo(get_chwnd(IDCW_HARMONICSRESULTSTIMESHIFTSB),SB_CTL,&sci); //gets the scroll info for the scrollbar
if (elapsed_time >= MAX_TIME_WIDTH_MS) //if elapsed time is longer then the max window length
{
start = _SECSTOMS(sci.nPos); //sets start to end - window length
end = start+MAX_TIME_WIDTH_MS; //set the end to current (scroll position / scroll max) * latest time -> this should put end relative to the scrollbar
}
else //if (elapsed_time <= MAX_TIME_WIDTH_MS)
{
start = g_h_minX; //start is min
end = g_h_maxX; //end is max
}
windowLengthMS = (end-start); //find the current window length
if(_MSTOSECS(windowLengthMS) > windowMaxS) //if the current window length beats one fo the markers
windowMaxS = ((windowMaxS < 10)?windowMaxS+1:(windowMaxS==40)?windowMaxS*1.5:windowMaxS*2); //change subdiv scaling
xScale = double(graph_width)/double(windowLengthMS); //set x-scale to width / window length
yScale = (double)(graph_height)/((double)max - (double)g_h_minY); //set y-scale to height / (maxVal-minVal)
if(!(g_h_maxY-g_h_minY))
yScale = 1;
if(!windowLengthMS)
xScale = 1;
int ticks = _MSTOSECS(elapsed_time); //ticks = full seconds elapsed
xTickStep = double(graph_width)/double(ticks+1); //tickstep = seconds+1 (accounts for 0)
yTickStep = double(graph_height)/double(Y_TICKS); //6 y-ticks, constant.
for the graph x-scaling
Related
I wanted to make a servomotor oscilate between 0-90 degrees when i push a button, but when i push another one, it stops oscillating and then remains in its latest position.
i started with this:
#include <Servo.h>
Servo myservo;
int pos = 0;
const int button1 = 5;
const int button2 = 6;
int lastpos = pos;
void setup() {
myservo.attach(3);
pinMode(button1, INPUT);
pinMode(button2, INPUT);
}
void loop() {
if(digitalRead(button1) == HIGH)
{for(pos = 0; pos <= 90; pos += 1)
{myservo.write(pos);
for(pos = 90; pos >= 0; pos -= 1)
{myservo.write(pos);
delay(36);
} } if(digitalRead(button2) == HIGH){ myservo.write(lastpos);}}}
To start, take a look at how to format code inside a question. It makes it a lot easier to read and help you out. See below for how I formatted for the site, but also readability.
Servo myservo;
int pos = 0;
const int button1 = 5;
const int button2 = 6;
int lastpos = pos;
void setup() {
myservo.attach(3);
pinMode(button1, INPUT);
pinMode(button2, INPUT);
}
void loop() {
if(digitalRead(button1) == HIGH) {
for(pos = 0; pos <= 90; pos += 1) {
myservo.write(pos);
for(pos = 90; pos >= 0; pos -= 1) {
myservo.write(pos);
delay(36);
}
}
if(digitalRead(button2) == HIGH) {
myservo.write(lastpos);
}
}
}
There are several issues with your code based on your description of what you are trying to achieve. First, let's start with the button presses. You are ready the button state, but your code will only detect the button if it is pressed at the exact moment you are doing the digital read. Here's a good resource for reading up on how to properly implement buttons on Arduino: https://www.logiqbit.com/perfect-arduino-button-presses
The second objective is to have the servo sweep back and forth, but stop when you press a button. Your for loops won't let that happen. As currently written, you will always do a sweep to one end and back and then check the next button.
You should update the position of the servo once each time through the loop so you can check on the status of the buttons. In pseudo-code, what I suggest you do is:
void loop() {
//Check button statuses
if(button1), start sweep
if(button2), stop sweep
//Update sweep position
if(ascending && pos < 90) {
//You should be increasing in angle and you haven't reached 90 yet
ascending = TRUE;
pos += 1
myservo.write(pos);
delay(36); //Or whatever delay you need for the servo
} else if(pos > 0) {
//You already reached 90 and are coming back down to 0
ascending = FALSE;
pos -= 1;
delay(36);
}
int x = 31;
int y = 31;
int x_dir = 4;
int y_dir = 0;
void setup ()
{
size (800, 800);
}
void draw ()
{
background (150);
ellipse (x,y,60, 60);
if (x+30>=width)
{
x_dir =-4;
y_dir = 4;
}
if (y+30>=height)
{
x_dir=4;
y_dir = 0;
}
if (x+30>=width)
{
x_dir = -4;
}
x+=x_dir;
y+=y_dir;
println(x,y);
}
Hi,
I have to create this program in processing which produces an animation of a ball going in a Z pattern (top left to top right, diagonal top right to bottom left, and then straight from bottom left to bottom right) which then goes backwards along the same path it came.
While I have the code written out for the forward direction, I don't know what 2 if or else statements I need to write for the program so that based on one condition it goes forwards, and based on another condition it will go backwards, and it will continue doing so until it terminates.
If I am able to figure out which two if statements I need to write, all I need to do is copy and reverse the x_dir and y_dir signs on the forward loop.
There are a ton of different ways you can do this.
One approach is to keep track of which "mode" you're in. You could do this using an int variable that's 0 when you're on the first part of the path, 1 when you're on the second part of the path, etc. Then just use an if statement to decide what to do, how to move the ball, etc.
Here's an example:
int x = 31;
int y = 31;
int mode = 0;
void setup ()
{
size (800, 800);
}
void draw ()
{
background (150);
ellipse (x, y, 60, 60);
if (mode == 0) {
x = x + 4;
if (x+30>=width) {
mode = 1;
}
} else if (mode == 1) {
x = x - 4;
y = y + 4;
if (y+30>=height) {
mode = 2;
}
} else if (mode == 2) {
x = x + 4;
if (x+30>=width) {
mode = 3;
}
} else if (mode == 3) {
x = x - 4;
y = y - 4;
if (y-30 < 0) {
mode = 2;
}
}
}
Like I said, this is only one way to approach the problem, and there are some obvious improvements you could make. For example, you could store the movement speeds and the conditions that change the mode in an array (or better yet, in objects) and get rid of all of the if statements.
I have this method here which renders my players movement. It swaps between 3 images, standing, left leg forward, and right leg forward.
It swaps images very fast so how can I change the speed of the rendering?
public static void renderUpwardWalking() {
ImageIcon[] frames = { CharacterSheet.up, CharacterSheet.upLeftLeg,
CharacterSheet.upRightLeg };
if (Key.up && Character.direction == "up") {
currentFrame++;
if (currentFrame == 3)
currentFrame = 1;
Character.character.setIcon(frames[currentFrame]);
} else if (!Key.up && Character.direction == "up") {
currentFrame = 0;
}
}
This is usually done on timer.
Decide on a frame pattern and a frequency. You seem to have chosen the frame pattern CharacterSheet.up, CharacterSheet.upLeftLeg, CharacterSheet.upRightLeg. Let's say you want to swap frame every 400 ms.
Get the time from a clock with sufficient resolution. System.nanoTime() is usually accurate enough.
long frameTime = 400L * 1000000L; // 400 ms in nanoseconds Edit
currentFrame = (System.nanoTime() / frametime) % frames.length;
You can change the scale of your currentFrame counter, and use its range to control your frame rate:
//Let this go from 1...30
int currentFrameCounter;
.
.
.
currentFrameCounter++;
if(currentFrameCounter == 30) currentFrameCounter = 0;
//Take a fraction of currentframeCounter for frame index ~ 1/10 frame rate
//Note care to avoid integer division
currentFrame = (int) (1.0*currentFrameCounter / 10.0);
Putting it all together in a general model:
int maxCounter = 30; //or some other factor of 3 -- controls speed
int currentFrameCounter;
public static void renderUpwardWalking() {
ImageIcon[] frames = { CharacterSheet.up, CharacterSheet.upLeftLeg,
CharacterSheet.upRightLeg };
if (Key.up && Character.direction == "up") {
currentFrameCounter++; //add
if(currentFrameCounter == maxCounter) currentFrameCounter = 0;
currentFrame = (int) (1.0*currentFrameCounter / (maxCounter/3.0));
Character.character.setIcon(frames[currentFrame]);
} else if (!Key.up && Character.direction == "up") {
currentFrame = 0;
}
}
I am developing an XNA game that is using Kinect. The player seen on the screen is the real image of the person who is playing in front of Kinect sensor. For eliminating the background and getting only the player's image I am doing these operations in kinect.AllFramesReady:
using (ColorImageFrame colorVideoFrame = imageFrames.OpenColorImageFrame())
{
if (colorVideoFrame != null)
{
//Getting the image of the colorVideoFrame to a Texture2D named colorVideo
}
//And setting its information on a Color array named colors with GetData
colorVideo.GetData(colors);
}
using (DepthImageFrame depthVideoFrame = imageFrames.OpenDepthImageFrame())
{
if (depthVideoFrame != null){
//Copying the the image to a DepthImagePixel array
//Using only the pixels with PlayerIndex > 0 to create a Color array
//And then setting the colors of this array from the 'colors' array by using MapDepthPointToColorPoint method, provided by Kinect SDK
//Finally I use SetData function in order to set the colors to a Texture2D I created before
}
}
But the performance is very low unsurprisingly. Because I have to use GetData for a color array with 640*480 = 307200 length (because of the ColorImageFormat) and SetData for another color array with 320*480 = 76800 length (because of the DepthImageFormat) in every frame!
I wonder if there is any other solutions for this problem, any alternatives for SetData and GetData maybe. Because I know that these functions moving data between the GPU and CPU and that is an expensive operation for big data. Thanks for any help.
The Kinect for Windows Toolbox comes with a "GreenScreen-WPF" example, which should provide some insight into processing the information. Because you are working in XNA there may be some differences, but the overall concepts should work between the two examples.
The example works by extracting multiple players. Here is the business end of the processing function:
private void SensorAllFramesReady(object sender, AllFramesReadyEventArgs e)
{
// in the middle of shutting down, so nothing to do
if (null == this.sensor)
{
return;
}
bool depthReceived = false;
bool colorReceived = false;
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
{
if (null != depthFrame)
{
// Copy the pixel data from the image to a temporary array
depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);
depthReceived = true;
}
}
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (null != colorFrame)
{
// Copy the pixel data from the image to a temporary array
colorFrame.CopyPixelDataTo(this.colorPixels);
colorReceived = true;
}
}
// do our processing outside of the using block
// so that we return resources to the kinect as soon as possible
if (true == depthReceived)
{
this.sensor.CoordinateMapper.MapDepthFrameToColorFrame(
DepthFormat,
this.depthPixels,
ColorFormat,
this.colorCoordinates);
Array.Clear(this.greenScreenPixelData, 0, this.greenScreenPixelData.Length);
// loop over each row and column of the depth
for (int y = 0; y < this.depthHeight; ++y)
{
for (int x = 0; x < this.depthWidth; ++x)
{
// calculate index into depth array
int depthIndex = x + (y * this.depthWidth);
DepthImagePixel depthPixel = this.depthPixels[depthIndex];
int player = depthPixel.PlayerIndex;
// if we're tracking a player for the current pixel, do green screen
if (player > 0)
{
// retrieve the depth to color mapping for the current depth pixel
ColorImagePoint colorImagePoint = this.colorCoordinates[depthIndex];
// scale color coordinates to depth resolution
int colorInDepthX = colorImagePoint.X / this.colorToDepthDivisor;
int colorInDepthY = colorImagePoint.Y / this.colorToDepthDivisor;
// make sure the depth pixel maps to a valid point in color space
// check y > 0 and y < depthHeight to make sure we don't write outside of the array
// check x > 0 instead of >= 0 since to fill gaps we set opaque current pixel plus the one to the left
// because of how the sensor works it is more correct to do it this way than to set to the right
if (colorInDepthX > 0 && colorInDepthX < this.depthWidth && colorInDepthY >= 0 && colorInDepthY < this.depthHeight)
{
// calculate index into the green screen pixel array
int greenScreenIndex = colorInDepthX + (colorInDepthY * this.depthWidth);
// set opaque
this.greenScreenPixelData[greenScreenIndex] = opaquePixelValue;
// compensate for depth/color not corresponding exactly by setting the pixel
// to the left to opaque as well
this.greenScreenPixelData[greenScreenIndex - 1] = opaquePixelValue;
}
}
}
}
}
// do our processing outside of the using block
// so that we return resources to the kinect as soon as possible
if (true == colorReceived)
{
// Write the pixel data into our bitmap
this.colorBitmap.WritePixels(
new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
this.colorPixels,
this.colorBitmap.PixelWidth * sizeof(int),
0);
if (this.playerOpacityMaskImage == null)
{
this.playerOpacityMaskImage = new WriteableBitmap(
this.depthWidth,
this.depthHeight,
96,
96,
PixelFormats.Bgra32,
null);
MaskedColor.OpacityMask = new ImageBrush { ImageSource = this.playerOpacityMaskImage };
}
this.playerOpacityMaskImage.WritePixels(
new Int32Rect(0, 0, this.depthWidth, this.depthHeight),
this.greenScreenPixelData,
this.depthWidth * ((this.playerOpacityMaskImage.Format.BitsPerPixel + 7) / 8),
0);
}
}
If you are interested in only a single player, you could look into using the player mask to more quickly extract the appropriate pixel set. You'd fi
using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
{
if (skeletonFrame != null && skeletonFrame.SkeletonArrayLength > 0)
{
if (_skeletons == null || _skeletons.Length != skeletonFrame.SkeletonArrayLength)
{
_skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
}
skeletonFrame.CopySkeletonDataTo(_skeletons);
// grab the tracked skeleton and set the playerIndex for use pulling
// the depth data out for the silhouette.
this.playerIndex = -1;
for (int i = 0; i < _skeletons.Length; i++)
{
if (_skeletons[i].TrackingState != SkeletonTrackingState.NotTracked)
{
this.playerIndex = i+1;
}
}
}
}
You can then step through the depth data to extract the appropriate bits:
depthFrame.CopyPixelDataTo(this.pixelData);
for (int i16 = 0, i32 = 0; i16 < pixelData.Length && i32 < depthFrame32.Length; i16++, i32 += 4)
{
int player = pixelData[i16] & DepthImageFrame.PlayerIndexBitmask;
if (player == this.playerIndex)
{
// the player we are tracking
}
else if (player > 0)
{
// a player, but not the one we want.
}
else
{
// background or something else we don't care about
}
}
I'm pulling this code from a control I use to produce a silhouette, so it does not deal with the color stream. However, making a call to MapDepthFrameToColorFrame at the appropriate time should allow you to deal with the color stream data and extract the corresponding pixels to the player's mask.
I'm using the Java2D TextLayout class together with a LineBreakMeasurer and an AttributedCharacterIterator to draw a piece of text into a box. The text is wrapped.
Profiling shows me that the code is very slow. Most of the time is lost in the method TextLayout.draw(..).
Does anyone have a suggestion for speed improvement?
// Get iterator for string
AttributedCharacterIterator iterator = attribText.getIterator();
// Create measurer
LineBreakMeasurer measurer = new LineBreakMeasurer(iterator, context);
// loop over the lines
int i = 1;
while (measurer.getPosition() < iterator.getEndIndex()) {
// Get line
TextLayout textLayout = measurer.nextLayout(w);
// get measurements
float ascent = textLayout.getAscent();
float descent = textLayout.getDescent();
float leading = textLayout.getLeading();
float size = ascent + descent;
// Move down to baseline
if( i == 1 ) {
if( coverType == CoverType.SPINE ) {
y = (box.height-size)/2;
y -= (size+leading)*(lines-1)/2;
} else if( vAlign == Alignment.Center ) {
y += (h-size)/2-(size+leading)*(lines-1)/2;
} else if( vAlign == Alignment.Bottom ) {
y += (h-size) - (size+leading)*(lines-1);
}
}
y += ascent;
// calculate starting point for alignment
float paintX = x;
switch( hAlign ) {
case Right: {
paintX = x + w - textLayout.getVisibleAdvance();
break;
}
case Center: {
paintX = x + (w - textLayout.getVisibleAdvance())/2;
break;
}
}
// Draw line
textLayout.draw(g2d, paintX, y);
// Move down to top of next line
y += descent + leading;
i++;
}
The relevant code snippet is shown above. attribText is an AttributtedString set before. context is the g2d.getFontRenderContext().
This post is rather old now so I hope you have found a solution that works for your needs. If you haven't here is something to think about. You only need to draw the text that is within the visible region. Since you know the y coordinate of each line it is easy to check to see if the y lies within the bounds of getVisibleRect(). Only painting the text that is necessary greatly improves performance (assuming of course that your text is longer than a single page).