How to use filmstrip images (multi row & multi column)

How can I use "filmstrip knob png"s in project ? There is a big file with knob animation frames in it and they are not seperate files. How can I use it like this? How do we set which value corresponds to which row and square in which column? Or which way should I use?

Create an AffineTransform, set the translation factor in the mouseDrag event handler, apply the transform in the paint() method … you can see an example here:

… note that this endless slider only has a very simple filmstrip texture (two states) instead of your 30 states, but the technique for extracting the appropriate frame from the texture is the same - calculate the transform, apply it during paint() … with something like:

g.setFillType(juce::FillType(sliderImage, sliderImageTransform));
2 Likes

Note though, that rotating the 3D renders defeats the purpose of the filmstrip.
It will rotate the lightings with the frame, which leads to a very weird impression. Usually the sun doesn’t follow your hand when rotating a knob :wink:

I would advice to use vertically aligned, that way each frame will be in a contiguous block of memory. In this pattern you (the machine) will have to piece together parts of each line.

Graphics::drawImage allows you to select which area to draw.

2 Likes

With the AffineTransform you’re not rotating the frames, you’re translating where in the texture map the image class is obtaining its pixels for display. The purpose of the AffineTransform is to Translate the view, not to Rotate the contents of the view.

1 Like

Ah ok, my bad. I didn’t look into your code.
But I don’t think AffineTransform is necessary. Using the drawImage is more straight forward…

2 Likes

The reason to use an AffineTransform is so that you can translate relative to the degree of movement… so if the user holds down the mouse and does a big drag, you translate directly to the texturemap location that is relative to the distance of the drag - rather than stepping through each frame linearly.

1 Like

Not sure I follow. Each paint() event is an individual call, I don’t see how it is related to the precious paint().

Plus, if you don’t use OpenGL, AffineTransform is a generic operation, so it might do more work than a simple pixel copy in drawImage.

1 Like

You make a short move, or a long move. Do you step through all the frames for the long move, or do you just ‘snap’ to the image, by way of the transform, that you need to display?

Either way, if you use drawImage you still have to calculate the transform to be applied to the view. Using an AffineTransform is just a clean way to do it, since you normalize the mouse drag distance to the final transform required to pull the pixels from the filmstrip.

2 Likes

“myNormalizedValue” would the value you’d like to display with the rotary
this is assuming the top left frame is = 0.f, and the bottom right frame is = 1.f

Untested code, just on top of my head, but the idea should work

void paint(juce::Graphics& g) override
{
	int frame_index = static_cast<int>(columns * rows * myNormalizedValue);

	g.drawImage(
		myImage,
		0,
		0,
		getWidth(),
		getHeight(),
		myImage.getWidth()/columns * (frame_index % columns),
		myImage.getHeight()/rows * (frame_index / rows),
		myImage.getWidth()/columns,
		myImage.getHeight()/rows);
}
2 Likes

I didn’t think using affine transform before, I should try that :smiley:

1 Like

currVal *= ( totalNumberOfFrames / maxVal );
	
int posX = (int)currVal % numberOfFramesRow;
int posY = (int)currVal / numberOfFramesRow ;

//draw frame
g.drawImage(*image, x, y, width, height, posX*frameSize, posY * frameSize, frameSize, frameSize, false);

maxVal is the maximum of slider. currVal is the current value (float variable). I map the current value in the first line of code. Then corresponding frame is drawn at (x,y) point on the window.
posX and posY is calculated assuming the frame width and frame height are equal to each other. I tested the code and works for this filmstrip image.

By the way, I thought I must take number of total frames into account. Actually, this filmstrip image has 11 columns and 9 rows, but consists of 101 knob images. So the last row contains only 2 images.

I calculate posX and posY using number of columns. Is it okay or am I mistaken in logic?

There is literally no reason to use multiple rows/columns in a filmstrip other than that is what the designer prefers to see on their puny little screens while they are pushing pixels around … and we all know those guys shouldn’t be allowed to dictate data structures … :wink:

Keep it simple, have as many columns as you have states, and have just one row with all states … also makes the affine translation easier to conceptualize …

!00%, except have one column.

It is much easier to cut an area in a vertical strip than in an horizontal. The memory is much more adjacent.

1 Like

Never thought of that!

currVal *= ( totalNumberOfFrames / maxVal );

watch out, you’re overwriting your currval, this may not be desired.

int posX = (int)currVal % numberOfFramesRow;
int posY = (int)currVal / numberOfFramesRow ;

this may only work if the columns amount is equal to the row amount

The rest looks okay. Now I’d advise you’d rearrange your sprite sheet into a column as advised by austrian and daniel, for both ease of use and optmization.