Author Topic: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?  (Read 27450 times)

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« on: November 12, 2015, 08:55:54 PM »
Hi all,

I'm trying to get an image added to a surface to fill the surface but maintain aspect ratio. What I mean is I have the preserve_aspect_ratio flag set to true, but that keeps the asset "inside" the container, meaning you'll get letter- or pillar-boxing if the image isn't exactly the size of the container. What I want to do is treat the smallest dimension of the image to be loaded as the maximum size of the corresponding container dimension and scale the other dimension accordingly, meaning that in such cases, parts of the image would be "clipped."

An example: if I have a container (a surface we'll say) that's 100x100 and I have a snap that's 100x50 (for easy math!), I'd want AM to be able to see it as 200x100, thus ensuring that the container is filled even if it means I'm missing the "sides" of my image.

I tried an unholy combination of width, height, and subimg properties and positioning, and while it worked most of the time (or appeared to), on rare occasions, something would cause it to flip out. Here's what I have (that doesn't completely work):

Code: [Select]
function sizeSnap (str_sig)
{
/**/
// Determine snap width/height (subimg_) and scale accordingly:
// Take the smaller value and scale to SNAP_SIZE
local r=1.0; // ratio
local dims={w=snap.subimg_width, h=snap.subimg_height};

// If we haven't already, let's adjust the snap size:
if (dims.w > dims.h) {
// Wider
r=(dims.w+0.0)/dims.h;
snap.height=SNAP_SIZE;
snap.width=SNAP_SIZE*r.tofloat();
}
else if (dims.w < dims.h) {
// Taller
r=(dims.h+0.0/dims.w);
snap.width=SNAP_SIZE;
snap.height=SNAP_SIZE*r.tofloat();
}
else {
// Equal
snap.width=SNAP_SIZE;
snap.height=SNAP_SIZE;
}

// Position in frame:
snap.x=(SNAP_SIZE-snap.width)/2.0;
snap.y=(SNAP_SIZE-snap.height)/2.0;
/**/
}

(This assumes the constant SNAP_SIZE is defined and "snap" is an artwork image added earlier in the code.)

It's mostly there, just on occasion, moving up or down the list will cause a freak out that, typically, results in an image appearing as a bunch of vertical bars (even on images that are wider than tall). Not sure where that's happening...

Any thoughts? Or is there a simpler way to do this?! Thanks!

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #1 on: November 14, 2015, 09:04:42 AM »
I am actually working on exactly this, trying to make a custom object to handle the adjustment. Just to clarify, what you are referring to would be a FILL ( cropped and centered ) in the specified dimensions?



A few different concerns/things to consider:

If the image is on its own surface that is already adjusted to the size you want, you could enable preserve_aspect_ratio (allow it to worry about the aspect) and then just scale and center to fill the surface.

If the image will not fill the parent surface (such as if fe is the parent surface ), you could modify subimg values. In this case though, you'd probably want to disable preserve_aspect_ratio and handle that yourself. You would also need to use a transition callback to make sure to get the correct texture_width/texture_height values of the image, then do the subimg x/y/w/h adjustments.

Ideally in my custom object, I want options to say how to align the fill - like FILL ( centered X/Y ), FILL_TOP ( centered X ), FILL_LEFT ( centered on Y ), FILL_BOTTOM ( centered on X ).

My main concern in doing this is that regardless, some images don't look good when cropped/filled. What images are you planning to use this with?

I'll be doing some work on AM stuff today, so I'll try to get mine working and add some code on this thread.

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #2 on: November 14, 2015, 09:45:49 AM »
Thanks for the response!

Yeah, I'm trying to "fill" in the sense you described above. Like how Windows' desktop wallpaper can be set to fill which crops either the top or bottom depending on the image's dimensions, as you show in your Metroid box art example.

I had tried creating a surface with the dimensions I wanted (let's say 720x720), but I was trying to adjust that object's size, rather than creating an intermediary. I think I'll give that a shot and see what happens.

I had tried manipulating the subimg_ properties, but once adjusted, it seems like sometimes the next image would maintain the (wrong) subimg_ values, though that might be a completely wrong assumption!

You're right: some images look like crap when cropped (filled), but I'm setting up a square layout (centered on the actual screen size) and has a square area for snaps (mostly title screens, though that could change). It's more for visual interest than anything more meaningful (in my case), but having the ability would be helpful, I think, so I'm definitely interested in seeing your custom object!

Here's the file I'm using as my background. The snap would sit inside the larger transparent area:



(though the transparent area appears dark gray here!)

Here's a screengrab of the layout "in action":



I need to add another image to sit behind everything to hide the pillar-boxing effect. The goal is to ultimately put this in a cab that uses a square portion of a vertically-oriented 42" lcd to get the best of both vertical and horizontal games without having to actually rotate a monitor,  but just in case I'm trying to set it up to look decent on a "standard" monitor, too. Sort of. I can't imagine anyone else would want to use this particular layout!  ;)

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #3 on: November 14, 2015, 10:21:31 AM »
Okay, here's a revisited attempt, placing the code in the transition callback, but it seems to be "one behind," if that makes any sense. Meaning, let's say I'm seeing DoDonPachi on the screen; the print statement for the rom name shows me Dimahoo (the previous entry in my list), etc. Am I missing something, or executing this branch at the wrong time? Just trying to wrap my head around when the callback should actually execute, or if I'm using the wrong ttype here:

Code: [Select]
function doScreenUpdates (ttype, var, ttime)
{
switch (ttype) {
/* other ttypes handled here */

case Transition.ToNewSelection:
local r=1.0; // ratio
local dims={w=snapContainer.subimg_width, h=snapContainer.subimg_height};
print ("rom: "+fe.game_info(Info.Name, 0)+"\n");
print ("dims: "+dims.w+", "+dims.h+"\n");

if (dims.w > dims.h) {
// Wider
r=dims.w.tofloat()/dims.h.tofloat();
snapContainer.height=SNAP_SIZE*r.tofloat();
}
else if (dims.h > dims.w) {
// Taller
r=dims.h.tofloat()/dims.w.tofloat();
snapContainer.width=SNAP_SIZE*r.tofloat();
}
else {
// Equal
snapContainer.width=SNAP_SIZE;
}

// Center snapContainer:
snapContainer.x=(SNAP_SIZE-snapContainer.width)/2.0;
snapContainer.y=(SNAP_SIZE-snapContainer.height)/2.0;
print ("snapContainer coords: "+snapContainer.x+", "+snapContainer.y+"\n-----\n");
break;
}
}

Thanks for any insight you're able to provide!

Actually, I noticed if I replace the "0" index in the fe.game_info() call with "var," it returns the proper rom name; however, the snapContainer object still holds a reference (image) to the prior selection. Apparently. Is there a way to "update" which snap we're dealing with through var, or am I (again!) thinking about things the wrong way? Again, thanks!

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #4 on: November 14, 2015, 11:19:39 AM »
ideally you probably want to do what I'm doing - create your own object with its own transition, like:

Code: [Select]

class FillArt
{
   ref = null;
   constructor( art, x, y, w, h, surface = ::fe )
  {
    ref = surface.add_artwork( x, y, w, h );
    fe.add_transition_callback( this, "on_transition" );
  }
 
  function on_transition( ttype, var, ttime )
  {
    switch( ttype )
    {
      case Transition.ToNewSelection:
      case Transition.ToNewList:
      case Transtion.StartLayout:
        //local texture_width = ref.texture_width;
        //local texture_height = ref.texture_height;
        //do scale/centering to ref
        //ref.subimg_width = x;
        //ref.subimg_height = y;
        local name = fe.game_info(Info.Name, var + ref.index_offset);
        break;
    }
    return false;
  }
}

FillArt("snap", 0, 0, 320, 240);

Just a quick response but hopefully this gets you in the right direction. ref here will be the reference to your actual artwork object.

Don't worry about your incorrect index info unless you are modifying something based on it, since you are just modifying the image properties. But I think it would be something like ( var + obj.index_offset ) as shown here. I tend to get confused since the var changes depending on which transition is occurring :)

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #5 on: November 14, 2015, 03:38:34 PM »
I'm about to demonstrate my lack of knowledge, but hey... :)

I looked through that example, and I can understand what's going on (I think), except for how you'd call/integrate an instance of that new class in the overall layout. (This might be a case where I display my confusion with Squirrel, too.)

Where I used something like this:

local snapcont=fe.add_artwork("snap", 0, 0, 720, 720);

would I now use something like:

local snapcont=FillArt("snap", 0, 0, 720, 720);

?

Wait, that can't be right, because I haven't specified what "ref" is (or is it understood, or is it "surface" passed directly in the constructor?). Or would there be a different way to instantiate it?

Wait (part 2: electric boogaloo)! I'd instantiate it on, say, snapCanvas like this, right?

local snapcont=FillArt("snap", 0, 0, 720, 720, snapCanvas)

?

Except in the constructor, you've got ref=surface.add_artwork(x, y, w, h) but no reference to the art itself?

I apologize for putting my ignorance on display, but I keep thinking about how I'd do things in ActionScript, and, well, Flash is pretty much dead (and I don't want to write yet another fe from the ground up anyway! Wouldn't even know where to begin!). Thanks for your patience!

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #6 on: November 14, 2015, 04:44:12 PM »
No problem.. when  you create the "instance" of the FillArt class, it will assign the artwork instance to ref:

Code: [Select]
local snapcont = FillArt("snap", 0, 0, 0, 0);
That gives you an instance of the FillArt class. The constructor is run with the parameters you passed above, and the constructor is what will set ref to an instance of the artwork returned by the add_artwork function.

The caveat of doing it like this is you can't use snapcont.width/.height/etc for your artwork properties... but you can access 'ref' using snapcont.ref.width/.height/etc

Make sense?
« Last Edit: November 14, 2015, 04:46:24 PM by liquid8d »

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #7 on: November 14, 2015, 05:34:10 PM »
I think I understand... here's where I am:

Code: [Select]
// Some other classes:
class FillArt
{
art=null;
constructor(ref, x, y, w, h, surface=::fe)
{
art=surface.add_artwork(ref, x, y, w, h);
art.preserve_aspect_ratio=true;

fe.add_transition_callback("onTransition");
}

function onTransition (ttype, var, ttime)
{
print ("onTransition\n");
switch (ttype)
{
case Transition.ToNewSelection:
case Transition.ToNewList:
case Transition.StartLayout:
local r=1.0; // Ratio
local dims={w=art.texture_width, h=art.texture_height};

if (dims.w > dims.h)
{
// Wider
r=dims.w.tofloat()/dims.h.tofloat();
art.width=::SNAP_SIZE*r;
art.height=::SNAP_SIZE;
}
else if (dims.w < dims.h)
{
// Taller
r=dims.h.tofloat()/dims.w.tofloat();
art.height=::SNAP_SIZE*r;
art.width=::SNAP_SIZE;
}
else
{
// Must be equal!
art.width=art.height=::SNAP_SIZE;
}

return false;
break;
}
}
};

That appears after my constant declarations (e.g., SNAP_SIZE). Later on, I have:

Code: [Select]
// Start with the snap: it needs to go beneath everything:
local snapCanvasCoords={x=72, y=58};
local snapCanvas=canvas.add_surface(SNAP_SIZE, SNAP_SIZE); // "canvas" is a surface created previously and added as fe.add_surface()
snapCanvas.x=snapCanvasCoords.x;
snapCanvas.y=snapCanvasCoords.y;

local snapContainer=FillArt("snap", 0, 0, SNAP_SIZE, SNAP_SIZE, snapCanvas);

The image (the snap) shows up where it should, but it appears as though the FillArt "onTransition" handler never fires. I tossed in a print statement just to see what was going on, but it never seems to output anything. Above, I haven't bothered attempting to position the art: just trying to ensure it scales appropriately, but it appears it doesn't scale at all... :(

What am I missing?! Thanks!

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #8 on: November 14, 2015, 05:57:21 PM »
you took out the 'this' :)

It should be:

Code: [Select]
fe.add_transition_callback( this, "onTransition");

'this' tells it to look in the class that the 'onTransition' function is in

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #9 on: November 14, 2015, 06:11:15 PM »
Gagh! Thanks! :)

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #10 on: November 14, 2015, 06:20:51 PM »
one other thing you'll want to correct - put the break after your code under the 'case' options in the switch, and THEN return false. Otherwise, if you try to add other ttype cases, it will run through all your code until the break is reached, which would be never it.. like so:

Code: [Select]
function transition( ttype, var, ttime )
{
   switch ( ttype )
   {
      case 1:
      case 2:
         //code for case 1 + 2
         break;
      case 3:
         //code for case 3
         break;
   }
   return false;
}


Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #11 on: November 14, 2015, 06:20:58 PM »
Success! I think! :) The below snippet seems to work as intended:

Code: [Select]
// Some other classes:
class FillArt
{
art=null;
constructor(ref, x, y, w, h, surface=::fe)
{
art=surface.add_artwork(ref, x, y, w, h);
art.preserve_aspect_ratio=true;

fe.add_transition_callback(this, "onTransition");
}

function onTransition (ttype, var, ttime)
{
switch (ttype)
{
case Transition.FromOldSelection:
case Transition.ToNewSelection:
case Transition.ToNewList:
case Transition.StartLayout:
local r=1.0; // Ratio
local dims={w=art.texture_width, h=art.texture_height};

if (dims.w > dims.h)
{
// Wider
r=dims.w.tofloat()/dims.h.tofloat();
art.width=SNAP_SIZE*r;
art.height=SNAP_SIZE;
}
else if (dims.w < dims.h)
{
// Taller
r=dims.h.tofloat()/dims.w.tofloat();
art.height=SNAP_SIZE*r;
art.width=SNAP_SIZE;
}
else
{
// Must be equal!
art.width=art.height=SNAP_SIZE;
}

art.x=(SNAP_SIZE-art.width)/2.0;
art.y=(SNAP_SIZE-art.height)/2.0;

// print ("r: "+r+"\ndims: "+dims.w+", "+dims.h+"\nw/h: "+art.width+", "+art.height+"\n");
break;
}
return false;
}
};

SNAP_SIZE is defined prior to instantiating the class, but having added the Transition.FromOldSelection to the list of ttypes to handle, things seem to be sizing in a correct enough way as of now! Woo!

(I reserve the right to attenuate my enthusiasm if I discover I'm wrong! ;) )

[Also, corrected "break" error as noted above!]
« Last Edit: November 14, 2015, 06:23:31 PM by Bgoulette »

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #12 on: November 14, 2015, 07:19:42 PM »
Great to hear! I didn't get to do much of anything today, so I'll try to check it out tomorrow. We can do some optimizations and add a few features to the class, then convert it to a module :)

Bgoulette

  • Sr. Member
  • ****
  • Posts: 116
  • I wrote a book.
    • View Profile
    • BlakeGoulette.com
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #13 on: November 15, 2015, 11:00:34 AM »
Great to hear! I didn't get to do much of anything today, so I'll try to check it out tomorrow. We can do some optimizations and add a few features to the class, then convert it to a module :)

Sounds good! Would love to be able to contribute in some small way!

Here's a slightly revised version of the class. Now it's not so tightly coupled to my particular implementation: I added _w and _h members to the class for assignment from the original instantiation call (versus using my SNAP_SIZE constant).

I also added a method for choosing an alignment option. It worked when called from within the class, but that's not how it should be used: it needs to be accessible from outside the class so you could call it from the primary guts of the layout, but I'm not sure how to do that in Squirrel just yet. I'll keep poking around. You'll see I'm referencing some static strings that don't actually exist: wasn't sure where to declare those (something akin to Transition.* is what I was trying to do: see below for the intended usage).

Anyway, here's the revised class with a lot of non-functional stuff in it (commented out):

[redacted]

And here's an example use case:

Code: [Select]
local myFilledArt=FillArt("snap", 0, 0, 720, 720, container); // container is a previously created surface or other image object
myFilledArt.set_anchor(FillArt.TopLeft);

But again, currently, that second line doesn't work...

Edit: I created an enum object to hold the anchor point strings and am in fact using that to set the initial position of the FillArt instance:

Code: [Select]
enum Anchor
{
TopLeft="TopLeft"
TopCentre="TopCentre"
TopRight="TopRight"
CentreLeft="CentreLeft"
Centre="Centre"
CentreRight="CentreRight"
BottomLeft="BottomLeft"
BottomCentre="BottomCentre"
BottomRight="BottomRight"
}

class FillArt
{
art=null;
_w=0;
_h=0;

constructor(ref, x, y, w, h, surface=::fe)
{
art=surface.add_artwork(ref, x, y, w, h);
art.preserve_aspect_ratio=true;

// Set internal properties for width/height:
_w=w;
_h=h;

fe.add_transition_callback(this, "onTransition");
}

function onTransition (ttype, var, ttime)
{
switch (ttype)
{
case Transition.FromOldSelection:
case Transition.ToNewSelection:
case Transition.ToNewList:
case Transition.StartLayout:
local r=1.0; // Ratio
local dims={w=art.texture_width, h=art.texture_height};

if (dims.w > dims.h)
{
// Wider
r=dims.w.tofloat()/dims.h.tofloat();
art.width=_w*r;
art.height=_h;
}
else if (dims.w < dims.h)
{
// Taller
r=dims.h.tofloat()/dims.w.tofloat();
art.height=_h*r;
art.width=_w;
}
else
{
// Equal
art.width=art.height=_w;
}

// Image is centered by default:
set_anchor(Anchor.Centre);
break;
}

return false;
}

/**/
// Function works when called from within FillArt, but not sure how to
// make it accessible from outside (incl. static members)...
function set_anchor (str_anchor)
{
::print ("anchor: "+str_anchor+"\n");
switch (str_anchor)
{
case Anchor.TopLeft:
art.x=0;
art.y=0;
break;

case Anchor.TopCentre:
art.x=(_w-art.width)/2.0;
art.y=0;
break;

case Anchor.TopRight:
art.x=_w-art.width;
art.y=0;
break;

case Anchor.CentreLeft:
art.x=0;
art.y=(_h-art.height)/2.0;
break;

case Anchor.CentreRight:
art.x=_w-art.width;
art.y=(_h-art.height)/2.0;
break;

case Anchor.BottomLeft:
art.x=0;
art.y=_h-art.height;
break;

case Anchor.BottomCentre:
art.x=(_w-art.width)/2.0;
art.y=_h-art.height;
break;

case Anchor.BottomRight:
art.x=_w-art.width;
art.y=_h-art.height;
break;

case Anchor.Centre:
default:
art.x=(_w-art.width)/2.0;
art.y=(_h-art.height)/2.0;
break;
}

return;
}
/**/
};

However, it still doesn't work when attempting to set the anchor position externally :(
« Last Edit: November 15, 2015, 11:45:11 AM by Bgoulette »

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #14 on: November 15, 2015, 03:53:08 PM »
alright got to check it out for a bit. I'm working on some updates, but still a bit away.

For learning purposes - functions within a class can work as either a static function - FillArt.set_anchor() or as a function of the class instance - my_art.set_anchor(). Since you will have multiple FillArt instances and you (might?) want each to have its own anchor setting - you want the latter. Within the class itself, you can just call set_anchor() like you did in the transition function and it will be setting the anchor for that instance.

Some changes I'm looking at:

- Store the anchor position in a variable. Then you can do a get ( local current = my_fillart.anchor; ) as well as pass the current setting to the set_anchor function.

- You had the anchor being reset to Centre each time the transitions occurs. Instead, you can now pass the current anchor setting to the set_anchor function.

- Instead of enum (which is apparently local to the script), I switched it to a table stored in the root table (::). Now we can make this a module, and Anchor will be available to the layout script.

- I gave the fill art its own surface (_parent) which the art is drawn on. Now you can properly move the object and the art fills the surface according to the anchor.

- I added aliases to the Anchor table for 'center' - because, well :)

I'll try to finish it up tonight, not sure if I'll get the time to yet or not.