Graphics.Scale(x,y) – this looks like a bug to me

Setting the value of Graphics.Scale doesn’t work as I would expect. It appears to be a “running value”.

  • If I set the scale to 2.0 and draw, I expect the size to be doubled. It is.
  • If I then set the scale to 1.0 and draw, I expect the size to be normal. It’s not. It’s still doubled.
  • The solution I found is to “revert” 2.0 to 1.0 by scaling 0.5, which means setting it to 1.0 doesn’t do anything, because what it does is apply the given value to the current value (running value).

That can’t be right. There’s no way to read the scale values and there’s nothing about this unusual behaviour in the LR. Here’s my test code (Xojo2024r3, Mac Studio M1):

// Draw a red square at scale 1x
g.DrawingColor = Color.Red
g.DrawRectangle( 0,0, 100,100 )
// Draw a blue square at scale 2x
g.Scale( 2.0 ,2.0 )
g.DrawingColor = Color.Blue
g.DrawRectangle( 2,2, 100,100 )
// Draw a green square at scale 1x
'g.Scale( 1.0 ,1.0 ) // <-- This doesn't work
g.Scale( 0.5 ,0.5 ) // <-- This works
g.DrawingColor = Color.Green
g.DrawRectangle( 4,4, 100,100 )

EDIT: after posting this I found elsewhere on the forum that apparently .Scale is part of .Translate … and then the above behaviour makes sense, sort of … But it’s not in the LR (!)

… The reason I say it “sort of makes sense” is that Graphics.Translate is a “running value”, although it looks like I only knew that from experience, because the LR says nothing about it either (!)

// Draw a red square at scale 1x
g.DrawingColor = Color.Red
g.DrawRectangle( 0,0, 100,100 )
// Draw a blue square at scale 2x
g.Scale( 2.0 ,2.0 )
g.DrawingColor = Color.Blue
g.Translate( 2,2 ) // puts the square at 2,2
g.DrawRectangle( 0,0, 100,100 )
// Draw a green square at scale 1x
'g.Scale( 1.0 ,1.0 ) // <-- This doesn't work
g.Scale( 0.5 ,0.5 ) // <-- This works (running value: 2.0 * 0.5 = 1.0 )
g.DrawingColor = Color.Green
g.Translate( 0,0 ) // puts the square at 2,2 (running value: 2 + 0 = 2 )
g.DrawRectangle( 0,0, 100,100 )

i think you look for

.SaveState
.RestoreState

https://documentation.xojo.com/api/graphics/graphics.html#graphics-savestate

Thanks, but no, that doesn’t have to do with the problem I’m describing: the values are not set as expected, they are calculated according to the existing values, which is totally non-intuitive and is not stated anywhere in the LR.

1 Like

Not a bug. You are setting the state of the graphics context. First, you’re scaling it up by 200%. Scaling it by 100% is a non-operation. :grin:

1 Like

I understand that’s what’s happening. It isn’t in the documentation.

This is actually how most high end graphics rendering systems work, including the macOS graphics subsystem. It dates back to how PostScript was architected and has huge advantages if you learn to embrace it.

Oh, gotcha.

that is frequently the case
that the documentation is a guess what.
or a single method is explained but not in context with others or the order of use.

basically the xojo ide should show a clear explanation pop over info for a method.

I get that, but because it’s not explained in the docs, it looks like a bug to anyone who doesn’t already expect that behaviour.

I often learn how something works while it’s the center of my attention for whatever project, and if I don’t use that thing after the project is over, I’ll forget it. Proper documentation is essential.

i agree 100% :slight_smile:

it would also help if the community can add online documentation.

From the docs:

Scale(x As Double, y As Double)

Sets the scale for the Graphics context as specified scaleX and scaleY

Yeah, that’s definitely not the wording I would choose. I would suggest "Scales the Graphics context by the specified factor, where 1.0 signifies “100% of current”.

1 Like

Markus, it looks like I just didn’t understand what you must have meant with this …

Now after I thought more about it, I think you must have meant this:

If one always uses g.SaveState and g.Restore state after every instance of changing g.Scale, then the problem goes away, because the state will always return to normal.

Therefore it isn’t hard to make a method to do that, like this:

Public Sub SetScale(extends g as Graphics, x as double, y as double)
  g.SaveState
  g.Scale( x, y )
  g.RestoreState
End Sub

But that would only work if one remembers to use it every time, and probably also if one is not doing anything else with Translate and Scale and Rotate and so on.

EDIT: actually this won’t work at all, because the drawing needs to be done before .RestoreState is called. Hm …

I guess for certain applications this is one of those cases where writing repetitive code becomes unavoidable.

But saving and restoring the context state is surely slow and expensive. Which most likely points to a design flaw.

The problem is, if one is already scaling pictures and drawing parts of pictures which are scaled, it’s convenient to be able to scale the results of that scaled and cropped drawing. That’s the situation I find myself in and the reason I reached for Graphics.Scale

Also totally non-intuitive and not in the LR (!) — After storing a graphics state using g.SaveState, you can only recall it once using g.RestoreState. The saved state gets released and can’t be recalled again. So if you want a persistently saved state to revert to, you have to store it again immediately every time after you restore it.

// save the normal state
g.SaveState
// do whatever Translate, Scale, Rotate, etc.
// revert to normal
g.RestoreState
g.SaveState // <- if you don't do this, restoring will do nothing next time

I suppose that too may be how these things typically work, but … why? It seems ridiculous. The saved state should remain there until it gets replaced by another saved state. Isn’t that what any normal person would expect?

this method would do nothing.

its better to understand if you programm open gl.
and have a look at Matrix mathematics.

use case

g.SaveState
// draw a clock-face with lines or dots in a circle
g.RestoreState

Apparently you didn’t see my EDIT above or the reply I posted after that.

If there is a point here, it seems to be that the documentation is very poor and misleading about the following items:

Graphics.Translate
Graphics.Scale
Graphics.SaveState
Graphics.RestoreState
Graphics.Rotate

These all behave in quite unexpected ways and the LR gives no clue as to what is happening.

The Translate, Rotate & Scale methods are standard 2D transformation matrix functions (for some reason Xojo haven’t implemented a method to supply the entire matrix which means you can’t shear or mirror).

The values you provide to the functions are used to build a matrix which is then multiplied with the matrix that exists in the graphics state (this website shows you what is happening behind the scenes: GraphicMaths - 2D transformation matrices)

Setting the scale back to 1 doesn’t work because it just multiplies the relevant parts of the graphics state matrix values by 1.

The only way to get back to a previous graphics state is to save the state beforehand and then restore it afterwards.

The state is a stack which is why you can’t save the state and then restore it multiple times. However, you can do things like this:
a) save the state
b) make changes to the state
c) save the state
d) make changes to the state
e) restore the state (which gets you back to state c)
f) restore the state (which get you back to state a).

2 Likes

Thanks, that’s all very helpful information which should obviously be mentioned in the documentation. It really wouldn’t take much. As I see it, these are the key points that are missing:

  • Translate, Rotate and Scale act on the currently stored values (2D transformations)
  • SaveState / RestoreState is a stack

That may be all that needs to be added.

Suggest you raise an Issue about those doc items.

1 Like

Good idea. Here it is: Issue #77627

2 Likes