View Full Version : Image rotation?
jkeller
April 14th, 2026, 15:07
Can anyone point me to documentation or examples related to image rotation?
I've tried several approaches, but no luck so far. This is for an extension where I want to flip some images.
Thanks!
Trenloe
April 14th, 2026, 15:58
There's an angle parameter as part of the layerdata table in Image.addLayer: https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/2165473281/Image#addLayer But this is when you add a layer, not changing an existing layer - depending on what you have on the layer, you may be able to remove then add the layer to effectively change the rotation. I don't know if this can be changed via another (undocumented?) function.
I do quite a bit with tiles in the FVTT -> Fantasy Grounds extension, you could look at the code in that extension - the addTile function in manager_fvtt2fg.lua is a good place to start. Extension information here: https://www.fantasygrounds.com/forums/showthread.php?85659-FVTT-to-Fantasy-Grounds-map-export-import
jkeller
April 14th, 2026, 16:30
Thanks for the response.
This is for a Tarot card extension (nothing related to the map), currently using a genericcontrol (I've tried tokens, and others) for the card.
Do your suggestions still apply, or are those more for map layers/tiles?
superteddy57
April 14th, 2026, 16:35
The quick way I would approach this is just making sideways images of the cards and set the asset to that card when turned.
Trenloe
April 14th, 2026, 16:44
Ah, OK - so you're not using an FG "image" control then, as you mentioned "image rotation" I assumed you were talking about an FG image control. The information I gage was for FG images, which aren't restricted to maps. As I'm not exactly sure how you're approaching this, the information I provided may not be of use to you.
jkeller
April 14th, 2026, 16:53
The quick way I would approach this is just making sideways images of the cards and set the asset to that card when turned.
That would work for sure. That's the approach I used for my Volvelle extension. But in this case, I have hundreds of card images, and I'm really hoping not to triple the storage needed (one for inverted, one for sideways).
Ah, OK - so you're not using an FG "image" control then, as you mentioned "image rotation" I assumed you were talking about an FG image control. The information I gage was for FG images, which aren't restricted to maps. As I'm not exactly sure how you're approaching this, the information I provided may not be of use to you.
I've tried some other controls, but not imagecontrol yet - I'll give that a try, thanks!
Tempered7
April 14th, 2026, 17:19
That would work for sure. That's the approach I used for my Volvelle extension. But in this case, I have hundreds of card images, and I'm really hoping not to triple the storage needed (one for inverted, one for sideways).
Just a question to help without any knowledge in coding:
Would it be possible to [with some sort of control] that one card image used for all rotations instead of using different card images for face up & sideways, etc?
Automating some function just like in the map making part of FGU?
jkeller
April 14th, 2026, 17:31
Just a question to help without any knowledge in coding:
Would it be possible to [with some sort of control] that one card image used for all rotations instead of using different card images for face up & sideways, etc?
Automating some function just like in the map making part of FGU?
That's what I'm hoping; investigating the imagecontrol now.
Currently, I have a version that adds a small down-arrow to indicate inversion. That works, it's just not very immersive.
Tempered7
April 14th, 2026, 17:39
Good to know. Seems like it's already progressing.
Right now I am running the deep research and in a few mins, after I check everything, I will make another post of my findings.
Tempered7
April 14th, 2026, 18:09
Can someone with actual knowledge check this So I can do corrections if necessary?
It's from a Deep Research of NotebookLM.
Sorry it took a while.
Ok, Im not 100% sure on what I've found but these are the ways to use one image per card face and one image for a card back with some controller combo.
Entire PDF is here (https://www.dropbox.com/scl/fi/4u0ja0u3ykfhmu1m8w5co/card-rotation.pdf?rlkey=pn10wedhuqg5aaqubcoo3dbpj&st=k28ybtk2&dl=0).
Summary
1. Define the Container
The foundation of the card display is the genericcontrol, defined in XML. This control acts as a widgetcontainer, providing a base canvas and mounting point for dynamic graphical assets.
2. Instantiate the Asset
To display the card, use Lua to create a bitmapwidget within the genericcontrol by calling:
addBitmapWidget(asset_name): This function instantiates a single asset (like a card face) as a widget object.
3. Apply Rotation and Orientation -- PROBLEMATIC PART!!! Check Carl's Post (https://www.fantasygrounds.com/forums/showthread.php?87166-Image-rotation&p=761963&viewfull=1#post761963) // It kinda worked (https://www.fantasygrounds.com/forums/showthread.php?87166-Image-rotation&p=761972&viewfull=1#post761972).
Orientation is handled programmatically through the setRotation function, which supports floating-point degree values for free rotation.
Face-Up (Standard): widget.setRotation(0). Degrees chart (https://www.shutterstock.com/image-vector/90-270-degree-angle-icons-600w-1873672033.jpg).
Inverted: widget.setRotation(180).
Sideways/Tapped: widget.setRotation(90) or (270).
Note: The parent genericcontrol must have a bounding box large enough to accommodate the card’s dimensions when rotated (e.g., a 300x500 card needs a 500x300 footprint when sideways).
4. Manage Face-Down States
A single image widget can be manipulated to show different states, or a dual-widget system can be used:
Asset Swapping: Call setBitmap("back_asset_name") on the existing widget to switch between the card face and back.
Visibility Toggling: Create two widgets (one for the face, one for the back) and use setVisible(boolean) to toggle which is shown.
5. Handle Stacking and Z-Order
When arranging cards in spreads or piles, use procedural anchoring to manage overlap and depth:
setAnchor(edge, parent, anchor, offset, relation): Defines the relative position of cards.
Stacking Logic: Setting the relation parameter to "relative" allows cards to stack or fan out automatically based on the position of the previous card in the list.
Z-Order: While XML order determines default drawing, dynamic reordering in a hand requires iterating through the card list and re-applying anchors to ensure the correct visual flow.
EDIT: RELATED WIKI PAGES FOR EASY ACCESS
genericcontrol (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996645055/genericcontrol)
Ruleset+-+Interfaces+Windows+Panels+Widgets (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644483/Ruleset+-+Interfaces+Windows+Panels+Widgets)
widgetcontainer (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644818/widgetcontainer)
widget (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644807/widget)
bitmapwidget (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644709/bitmapwidget)
setVisible(boolean) (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644788/tokeninstance#setVisible)
setAnchor (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644833/windowcontrol#setAnchor)
NEW setOrientation (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644788/tokeninstance#setOrientation) under tokeninstance (https://fantasygroundsunity.atlassian.net/wiki/x/tJdnOw).
Tempered7
April 14th, 2026, 20:42
List of Extensions with Image Manipulation
Fantasy Grounds Unity (FGU) extensions that utilize image manipulation through the FGU API, particularly functions like setRotation, setBitmap, setAnchor, and the Widget API.
Decked Out (https://forge.fantasygrounds.com/shop/items/910/view): Created by Saagael, this extension uses tokens as card images and manages face-down states by toggling visibility between face and back widgets. It also includes functional innovations like "flip" and "peek" mechanics and enlarged hover images for legibility.
Carried Combatants (https://forge.fantasygrounds.com/shop/items/615/view): These combined extensions feature a Synchronized Movement & Rotation system. When a "carrier" token (like a vehicle) is rotated using setRotation, the extension programmatically calculates and updates the orientation and position of all "carried" tokens to match.
FG-5E-Enhancer (https://github.com/VikingStudio/FG-5E-Enhancer): This extremely complex, OUTDATED utility uses bitmap widgets to add dynamic graphical data to tokens. It manipulates images to display horizontal health bars, blood splatters, and "blood pools" that appear under tokens upon death. It also uses text widgets for altitude tracking.
Natural Selection (https://forge.fantasygrounds.com/shop/items/953/view): This extension handles spatial arbitration when tokens are stacked on the same grid space. It uses a pop-up window (built with a genericcontrol) to show token icons and allows users to manipulate Z-order to bring a specific token to the top of the stack.
Aura Effects (https://forge.fantasygrounds.com/shop/items/32/view): Managed by Bmos, this extension renders complex geometric shapes like oriented 3D rectangles (3drect). These overlays use an orientation parameter to synchronize the aura’s rotation with the source token or to represent directional effects like breath weapons.
Generic Actions (https://forge.fantasygrounds.com/shop/items/152/view): This extension utilizes the ImageLayer API to place graphics, such as arrows or area-of-effect templates, directly onto map layers.
EDIT: LINKS ARE ADDED.
jkeller
April 14th, 2026, 20:45
Thanks for that! I'll let you know how it goes :)
Tempered7
April 14th, 2026, 20:54
-double post-
Tempered7
April 14th, 2026, 20:56
Thanks for that! I'll let you know how it goes :)
Good luck! And fingers crossed xD
Sometimes it combines things that it shouldnt so it might not be 100% accurate.
jkeller
April 15th, 2026, 13:17
Orientation is handled programmatically through the setRotation function, which supports floating-point degree values for free rotation.
I don't see setRotation in the documentation. I tried calling it, and I don't get a "missing function" error, so that's good. But it doesn't seem to do anything.
I tried setRadialPosition(0, 50), and that does move the widget, but does not rotate it.
pindercarl
April 15th, 2026, 16:15
I don't see setRotation in the documentation. I tried calling it, and I don't get a "missing function" error, so that's good. But it doesn't seem to do anything.
I tried setRadialPosition(0, 50), and that does move the widget, but does not rotate it.
I took a quick look at the code and while widget does expose a LUA function "setRotation", it is only used by the text widget. If I recall correctly, John added that to rotate the text on the character sheet tabs. Historically, the tabs used different images for each tab. It may only support rotation in 90-degree increments, but I didn't dig far enough to verify. Rotating the text widget does rotate the frame of the widget. You could, theoretically draw the cards with frames. I'm definitely not an expert on rulesets or extensions. I can look at the scripts registered to the API, but I'm not familiar with their actual usage.
Tempered7
April 15th, 2026, 18:54
I think the AI picked it up from very old documents and combined it as a whole [as an answer to how do you rotate images].
setOrientation and setOrientationMode under tokeninstance is there, but I couldnt find setRotation in the wiki/pdf I have [network problems prevent me from entering it right now].
Tempered7
April 15th, 2026, 19:06
I found where the AI gone wrong: setRotation is a Unity function, not FGU function except what carl said.
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Rigidbody2D.SetRotation.html
setOrientation (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644788/tokeninstance#setOrientation) under tokeninstance (https://fantasygroundsunity.atlassian.net/wiki/x/tJdnOw) does a similar thing I guess.
function setOrientation(orientation)
Sets the orientation, i.e. rotation state, of the token.
Parameters
orientation (number)
The desired new rotation value
pindercarl
April 15th, 2026, 19:13
I found where the AI gone wrong: setRotation is a Unity function, not FGU function except what carl said.
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Rigidbody2D.SetRotation.html
setOrientation (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644788/tokeninstance#setOrientation) under tokeninstance (https://fantasygroundsunity.atlassian.net/wiki/x/tJdnOw) does a similar thing I guess.
LLMs are not a very good match for documenting the FG API. The "accuracy" of an LLM is directly proportional to the amount input data and there is not enough data for the LLM to be reasonably predictive. Lacking enough data it will hallucinate results based on other APIs. However, setRotation is a registered LUA API function of the base widget class and does rotate text widgets.
67126
Tempered7
April 15th, 2026, 19:15
LLMs are not a very good match for documenting the FG API. The "accuracy" of an LLM is directly proportional to the amount input data and there is not enough data for the LLM to be reasonably predictive. Lacking enough data it will hallucinate results based on other APIs. However, setRotation is a registered LUA API function of the base widget class and does rotate text widgets.
67126
Unfortunately yes, Carl. At least its a start point though.
EDIT: Oh, maybe it can be used as a text rotation over cards?
While Im at it, I checked all the other functions in the Summary, and they are all there. Z-Order is under many topics. Only setRotation is not in the wiki.
But I have no idea if those can actually be used for the same thing such as cards as token, etc.
jkeller
April 15th, 2026, 20:07
You could, theoretically draw the cards with frames. I'm definitely not an expert on rulesets or extensions. I can look at the scripts registered to the API, but I'm not familiar with their actual usage.
Wow, that's a weird trick I never would have thought of - but it looks like it might work! Thanks for the suggestion :)
jkeller
April 15th, 2026, 20:49
Yep, it worked!
67128
Do you happen to know if frame images can be scaled (like other icons), or will I need to resize the images (or the UI space for them)?
Here's the code in case someone is interested:
function showCardFaceUp(cCard, nCardId, x, y, w, h) -- rotatable
local aCard = AllCards[math.abs(nCardId)];
if not aCard then print("[Tarot Reading] Missing Card: " .. tostring(nCardId) .. " of " .. #AllCards); return; end
if cCard.textWidget then
cCard.textWidget.destroy();
cCard.textWidget = nil;
end
local wText = cCard.addTextWidget(nil, " "); -- text can't be empty!
cCard.textWidget = wText;
local textW, textH = wText.getSize();
local sFrame = "F_" .. getIconName(aCard); -- add frame prefix
wText.setFrame(sFrame, (w - textW) / 2, (h - textH) / 2, (w - textW) / 2, (h - textH) / 2); -- left, top, right, bottom
if nCardId < 0 then wText.setRotation(180); end -- inverted
cCard.setStaticBounds(x, y, w, h);
cCard.setVisible(true);
cCard.setEnabled(true);
end
<framedef name="F_TRT0_Magician"><bitmap file="graphics/tarot/01_Magician.jpg" /><offset>0,0,0,0</offset></framedef>
<template name="card">
<stringcontrol>
<anchored width="300" height="500"><top offset="0" /><left offset="0" /></anchored>
<invisible/>
<script>
function onClickDown(nButton, x, y)
return true; -- necessary for onClickRelease
end
function onClickRelease(nButton, x, y)
Tarot.mouseClickOnLargeCard(window, self);
return true;
end
</script>
</stringcontrol>
</template>
Tempered7
April 15th, 2026, 21:31
OMG! It's working, it's workiing! :D
Carl said
It may only support rotation in 90-degree increments, but I didn't dig far enough to verify.
If so does that mean cards can be turn sideways with -270 degrees?
Tempered7
April 15th, 2026, 21:42
Yep, it worked!
Do you happen to know if frame images can be scaled (like other icons), or will I need to resize the images (or the UI space for them)?
So hyped! so couldnt wait a pro answer but this is what LM said [maybe it gives a hint]:
Nine-Slice Logic: The frame bitmap is divided into nine sections. The four corners remain static (preserving their original dimensions), while the top, bottom, and side edges are stretched to match the width and height of the control. The center of the frame is either tiled or stretched to fill the background.
The Role of Offsets: You define these stretchable regions using the <offset> tag (consisting of four comma-separated numbers for Left, Top, Right, and Bottom). These margins tell the engine which parts of the image are corners and which are the "stretchable" edges.
Anchoring?
<anchor > ... </anchor>
The edge used from the parent panel: "left", "right", "right" or "bottom"
EDIT:
Or there is <GETgridoffset (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996645070/imagecontrol#getGridOffset)> under imagecontrol (https://fantasygroundsunity.atlassian.net/wiki/x/zphnOw).
And setGridOffset (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996645070/imagecontrol#setGridOffset)
<gridoffset > ... </gridoffset>
The X and Y coordinates to offset the first grid square from the upper left corner of the map. The default value is (0,0).
getGridoffet
function getGridOffset()
Returns the offset of the first grid square in the top left corner of the image. The offsets will be different from zero as a result of the grid not being perfectly aligned with the top left corner of the image itself. The values will vary from zero to -((grid size) - 1).
Return values
(number)
The horizontal offset of the grid
(number)
The vertical offset of the grid
---
setGridoffset
function setGridOffset(offsetx, offsety)
Sets the offset of the first grid square in the top left corner of the image. The valid values for the offsets range from zero to -((grid size) - 1), any values not in this range will be converted to fit the interval.
Parameters
offsetx (number)
The horizontal grid offset
offsety (number)
The vertical grid offset
Tempered7
April 15th, 2026, 22:00
Alternatively, If things upstairs doesnt work I found this:
Icons vs. Frames: Unlike frames, icons and bitmaps in a genericcontrol or bitmapwidget can be scaled more simply using draw modes like "fit" or "fill", which adjust the asset size to the control's dimensions without the nine-slice logic.
getDrawMode (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996645055/genericcontrol#getDrawMode) under genericcontrol (https://fantasygroundsunity.atlassian.net/wiki/x/v5hnOw)
<genericcontrol >
<icon > ... </icon>
<iconcolor > ... </iconcolor>
<drawmode> ... </drawmode>
</genericcontrol>
drawmode
Valid values are: ““ (default), “fill”, “fit”.
”” = The default draw mode will only adjust icon/asset size if too large for control size. Otherwise, icon/asset is drawn at original dimensions in the center of the control.
”fill” = Scale asset to fill control.
”fit” = Scale asset to fit control.
getDrawMode
function getDrawMode()
Get the draw mode assigned to this control.
Return values
(string)
The draw mode string set for this control (See Definition above.)
jkeller
April 15th, 2026, 22:49
OMG! It's working, it's workiing! :D
Carl said
If so does that mean cards can be turn sideways with -270 degrees?
I've only tried 180 so far, but 90 or 270 should work, and would be all we need (even if it only does increments of 90).
I'll try that "fit" or "fill" now, sounds promising. Without those, too-large images are clipped, and too-small images are tiled.
Thanks for all the help everyone!
Tempered7
April 15th, 2026, 22:52
Good to know!
Thanks for doing this J, I appreciate your time and your extensions, and this is actually so much fun! :)
Best of luck.
EDIT: I think I saw minus degrees somewhere in the wiki. So it might be useful.
pindercarl
April 15th, 2026, 23:56
I've only tried 180 so far, but 90 or 270 should work, and would be all we need (even if it only does increments of 90).
I'll try that "fit" or "fill" now, sounds promising. Without those, too-large images are clipped, and too-small images are tiled.
Thanks for all the help everyone!
It looks like it accepts arbitrary angles. Due to the way we render text, it doesn't look good unless the text is at right angles. For a frame, it should be okay.
Tempered7
April 16th, 2026, 00:11
It looks like it accepts arbitrary angles. Due to the way we render text, it doesn't look good unless the text is at right angles. For a frame, it should be okay.
Thanks for the good news.
Does it accept minus degress for the opposite directions?
Such as -270 for sideways? Or how to exactly turn a card sideways?
pindercarl
April 16th, 2026, 00:17
Thanks for the good news.
Does it accept minus degress for the opposite directions?
Such as -270 for sideways? Or how to exactly turn a card sideways?
It does not accept negative numbers. -270 should be the same as 90. 360 (since 0 and 360 are the same) minus 270 equals 90.
As for your previous question, do frames scale? Not in the way that you would want. The image for the frame tiles to fill the available space.
Tempered7
April 16th, 2026, 00:21
Thank you so much!
EDIT: The wide table / small card images when you pick 3 cards can be repurposed for the dungeon spread in Deck of Many Things.
https://www.fantasygrounds.com/forums/attachment.php?attachmentid=67120&d=1776179328
jkeller
April 16th, 2026, 01:18
As for your previous question, do frames scale? Not in the way that you would want. The image for the frame tiles to fill the available space.
Confirmed; no luck with scaling. But that was a minor issue; I just resized the images, and it looks pretty good. The rotation works great. Thanks all!
67134
pindercarl
April 16th, 2026, 01:20
Confirmed; no luck with scaling. But that was a minor issue; I just resized the images, and it looks pretty good. The rotation works great. Thanks all!
67134
Looks great. You could add an alpha channel to the source images to round off those corners.
Tempered7
April 16th, 2026, 01:49
Looks great. You could add an alpha channel to the source images to round off those corners.
In the current version, the corners fit nicely on 4 settings as single or 3 cards.
jkeller
April 16th, 2026, 13:13
Looks great. You could add an alpha channel to the source images to round off those corners.
Yeah, I did that for the backs (for some decks), since there's only one back image. But to do it for the face-up cards, I'd need to use PNG, and I *think* JPG is much smaller (at least, in my limited testing). I'm not sure if there are better alpha-channel formats that FG supports (maybe WEBP?); if so, I could do that. My knowledge is pretty old, just GIF/JPG/PNG heh.
pindercarl
April 16th, 2026, 15:47
Yeah, I did that for the backs (for some decks), since there's only one back image. But to do it for the face-up cards, I'd need to use PNG, and I *think* JPG is much smaller (at least, in my limited testing). I'm not sure if there are better alpha-channel formats that FG supports (maybe WEBP?); if so, I could do that. My knowledge is pretty old, just GIF/JPG/PNG heh.
Definitely use webp. It's the recommended format even if you aren't using alpha channels. Webp at 75% quality will give similar results to a JPEG but will be significantly smaller.
pindercarl
April 17th, 2026, 22:50
If you just need a simple flip/rotation, try handling it with CSS first—it’s the easiest for extensions:
CSS:
img {
transform: rotate(180deg); /* or scaleX(-1) for horizontal flip */
}
If that doesn’t work (e.g., dynamic images), use JavaScript + canvas to redraw and rotate the image.
Also check:
If images are loaded dynamically (you may need a MutationObserver)
Cross-origin restrictions when using canvas (CORS issues)
Start simple with CSS, then move to JS if needed.
This is the strangest LLM reply I've seen. It doesn't try to get me to click on a link or anything.
Tempered7
April 17th, 2026, 22:56
This is the strangest LLM reply I've seen. It doesn't try to get me to click on a link or anything.
When bots want to be helpful. LOL :D
jkeller
April 18th, 2026, 20:48
Nevermind, it just started working again.... just a glitch I guess....
jkeller
April 20th, 2026, 01:58
Getting a really weird bug.
Sideways (90 degree rotation) cards/tokens (even square ones!) are sometimes clipped.
I got rid of my inner subwindow (for testing), but it still happens.
Even though there's plenty of room, the sideways ones sometimes are clipped. I have not seen it happen if the rotation is 180.
I'm trying various random tweaks, but no luck so far. It seems like a bug in the underlying code :(
Any suggestions?
67157
jkeller
April 20th, 2026, 14:09
Here's an example of a related bug. This one makes sense to me. This is with an inner window, and an inverted (rotation = 180) card.
As the UI is scrolled, the code detects that there isn't enough room to draw the frame and clips it. However, it doesn't realize the frame has been rotated, so it clips the wrong edge, and fails to clip the correct edge (thus not drawing what it should, and also drawing what it shouldn't - outside the window).
67168
Powered by vBulletin® Version 4.2.1 Copyright © 2026 vBulletin Solutions, Inc. All rights reserved.