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

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 #15 on: November 15, 2015, 06:08:01 PM »
Looking forward to seeing what you come up with! Just for kicks, I tried going back and implementing the changes you describe. With spectacular failures all around! :( Seeing what you've modified I'm hoping will also help me get a better grasp on how you do things in Squirrel.

And the only reason I spelled center "the wrong way" ;) was because somewhere, I think for aligning text, the centering version is Align.Centre, which trips me up every time! I was trying to stay consistent, but yeah, I much prefer spelling it "the right way!" ;) hehe

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #16 on: November 16, 2015, 10:25:30 AM »
okay, here's what I have - probably still some outstanding issues:

http://imgur.com/a/MQbUQ

As I mentioned, here's some changes which you'll notice:
 * this code can now be put in modules/fill-art.nut and used with fe.load_module("fill-art");
 * modified anchor to squirrel root table, and modified values to be less confusing
 * each FillArt object has its own surface (parent) - the art objects x/y/w/h is adjusted based on aspect of parent and texture
 * moved aspect/anchor code to update function ( because we will need to update at other times, apart from the transition which is not yet implemented )
 * added scale property - 1.0 will fit to parent with cropping, > 1.0 will scale texture up, < 1.0 would not fill parent
 * added _get/_set functions - this will redirect properties to the parent surface so  you can use things like my_fillart.x/.y/.width/.height/.alpha/etc.. ( this still needs some modifications )
 * added a neat scale animation ( my_fill_art.animate = true ) with some options (described below)

Code: [Select]
/////////////////////////////////////////////////////////
//
// Attract-Mode Frontend - FillArt Module
//
/////////////////////////////////////////////////////////
//
// FillArt - creates an artwork that will fill the specified dimensions,
// cropping the art as necessary - but keeping aspect ratio.
//
// Example:
// local my_art = FillArt( 50, 50, 400, 200 );
// my_art.anchor = Anchor.Bottom;   //set where texture is anchored to ( if necessary based on object size / texture size / scale )
// my_art.scale = 1.0;              //set texture scale
// my_art.animate = true;              //whether to use a scale animation
// my_art.animate_to = 1.5;            //scale to animate to
// my_art.animate_ms = 10000;          //animation time in ms
//

::Anchor <-
{
    TopLeft = "TopLeft",
    Top = "Top",
    TopRight = "TopRight",
    Left = "Left",
    Centre = "Center",
    Center = "Center",
    Right = "Right",
    BottomLeft = "BottomLeft",
    Bottom = "Bottom",
    BottomRight = "BottomRight"
}

class FillArt
{
    VERSION = 1.0;      //first version
    art = null;         // the art object
    anchor = null;      // what side of the surface to anchor to
    scale = 1.0;        // 1.0 will fill parent - cropping edges
                        // > 1.0 will fill parent and scale larger
                        // < 1.0 will not fill but still center / account for anchor
   
    animate = false;        // whether to do a scale animation on transitions
    animate_ms = 5000;      // length of animate in ms
    animate_to = 1.5;       //scale to animate to
   
    _animate_start = 0;            //time animation started
    _animate_running = false;      //true if animation is currently running
    _animate_scale = 1.0;          //current animation scale
   
    _parent = null;     // the parent surface we will fill
    _texture_width = 0;   // store art texture width, once we get it
    _texture_height = 0;  // store art texture height, once we get it
   
    constructor(ref, x, y, w, h, surface=::fe)
    {
        //defaults
        anchor = ::Anchor.Center;
       
        //create the parent surface - which we will fill with the art
        _parent = surface.add_surface( w, h );
        _parent.x = x;
        _parent.y = y;
       
        //debug purpose - helps us see the parent surface size
        //_parent.add_image(::fe.script_dir + "pixel.png", 0, 0, w, h);
       
        //add the artwork to the parent
        art = _parent.add_artwork(ref, x, y, 0, 0);

        //add callbacks
        ::fe.add_transition_callback( this, "onTransition" );
        ::fe.add_ticks_callback( this, "onTick" );
    }
   
    //redirect undefined properties to the parent surface object
    function _get( idx ) { return _parent[idx]; }
    function _set( idx, val ) {
        _parent[idx] = val;
        return val;
    }
   
    function onTransition (ttype, var, ttime)
    {
        if ( ttype == Transition.FromOldSelection || ttype == Transition.ToNewList )
        {
            _texture_width = art.texture_width;
            _texture_height = art.texture_height;
            update();
        }
        //do animation
        if ( animate && ( ttype == Transition.ToNewSelection || ttype == Transition.StartLayout || ttype == Transition.ToNewList ) )
        {
            _animate_running = true;
            _animate_start = get_time();
        }
        return false;
    }
   
    function get_time() { return ::clock() * 1000; }
   
    function do_animation()
    {
        if ( _animate_running )
        {
            local elapsed = get_time() - _animate_start;
            local animate_progress = ( elapsed / animate_ms.tofloat() );
            _animate_scale = scale + ( animate_to - scale ) * animate_progress;
            //::print( "animate progress: " + animate_progress + " : " + _animate_scale + "\n" );
            //end animate
            if ( animate_progress >= 1.0 ) _animate_running = false;
            update();
        }
    }
   
    function onTick( ttime )
    {
        if ( animate && _animate_running ) do_animation();
    }
   
    function update()
    {
        local current_scale = ( animate ) ? _animate_scale : scale;
        local parent_aspect = _parent.width.tofloat() / _parent.height.tofloat();
        local texture_aspect = _texture_width.tofloat() / _texture_height.tofloat();
        if ( parent_aspect >= 1.0 )
        {
            //fill to parent width
            if ( texture_aspect >= 1.0 )
            {
                //wide texture
                //::print("wide parent, wide tex\n");
                art.width = ( _texture_width * ( _texture_width / _parent.width.tofloat() ) ) * current_scale;
            } else
            {
                //tall texture
                //::print("wide parent, tall tex\n");
                art.width = ( _texture_width * ( _parent.width / _texture_width.tofloat() ) ) * current_scale;
            }
            art.height = art.width / texture_aspect;
        } else
        {
            //fill to parent height
            if ( texture_aspect >= 1.0 )
            {
                //::print("tall parent, wide tex\n");
                //wide texture
                art.height = ( _texture_width * ( _parent.height / _texture_height.tofloat() ) ) * current_scale;
            } else
            {
                //tall texture
                //::print("tall parent, tall tex\n");
                art.height = ( _texture_width * ( _parent.height / _texture_width.tofloat() ) ) * current_scale;
            }
            art.width = art.height * texture_aspect;
        }
       
        switch ( anchor )
        {
            case ::Anchor.TopLeft:
                art.x = 0;
                art.y = 0;
                break;
            case ::Anchor.Top:
                art.x = ( _parent.width - art.width ) / 2.0;
                art.y = 0;
                break;
            case ::Anchor.TopRight:
                art.x = _parent.width - art.width;
                art.y = 0;
                break;
            case ::Anchor.Left:
                art.x = 0;
                art.y = ( _parent.height-art.height ) / 2.0;
                break;
            case ::Anchor.Right:
                art.x = _parent.width - art.width;
                art.y = ( _parent.height - art.height ) / 2.0;
                break;
            case ::Anchor.BottomLeft:
                art.x = 0;
                art.y = _parent.height - art.height;
                break;
            case ::Anchor.Bottom:
                art.x= ( _parent.width - art.width ) / 2.0;
                art.y= _parent.height - art.height;
                break;
            case ::Anchor.BottomRight:
                art.x = _parent.width - art.width;
                art.y = _parent.height - art.height;
                break;
            case ::Anchor.Center:
            default:
                art.x = ( _parent.width-art.width ) / 2.0;
                art.y = ( _parent.height-art.height ) / 2.0;
                break;
        }
        //::print("anchor: " + anchor + " texture size: " + " - " + current_scale + "\n" );
    }
}

« Last Edit: November 16, 2015, 11:29:57 AM by liquid8d »

verion

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 861
    • View Profile
    • new projects
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #17 on: November 16, 2015, 12:20:53 PM »
This is great! Does it work with videos too?

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 #18 on: November 16, 2015, 12:23:07 PM »
Wow, that's a lot of changes! :)

I did notice that, for some reason, certain snaps don't resize appropriately. Not sure why that might be, unless it's a math thing under the hood for those elements.

I revised my code (stealing liberally from parts of yours) for a "no-frills" version that works as it should (apparently) as a module, but without the animation or convenience features you included. I'll revisit it later, perhaps, though there's probably no need!

Edit: code! Save as fill-art-bg.nut in the modules folder, load with fe.load_module("fill-art-bg"):

Code: [Select]
::Anchor <-
{
TopLeft=7,
Top=8,
TopRight=9,
Left=4,
Center=5,
Right=6,
BottomLeft=1,
Bottom=2,
BottomRight=3
};

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

constructor(ref, x, y, w, h, surface=::fe)
{
anchor=::Anchor.Center;
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:
this.set_anchor(anchor);
break;
}

return false;
}

function set_anchor (int_anchor)
{
switch (int_anchor)
{
case ::Anchor.TopLeft:
art.x=0;
art.y=0;
break;

case ::Anchor.Top:
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.Left:
art.x=0;
art.y=(_h-art.height)/2.0;
break;

case ::Anchor.Right:
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.Bottom:
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.Center:
default:
art.x=(_w-art.width)/2.0;
art.y=(_h-art.height)/2.0;
break;
}

return;

function get_anchor ()
{
return anchor;
}
}
/**/
};

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #19 on: November 16, 2015, 12:51:58 PM »
This is great! Does it work with videos too?

Artwork only right now, so that does include artwork video.. but I could easily modify it to work with standard images/videos as well.

I know I made quite a few changes - don't want to ruin your learning experience with squirrel, but I was looking for something very similar as well :) It should be clear enough what's what and if you have any questions, just let me know.

I've been playing around and you can do some pretty cool setups :) It would be really nice to work with "fan art" but looks pretty good with other art too.

Pretty sure there is still some incorrect things for wider textures, but hopefully get those ironed out.

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 #20 on: November 16, 2015, 01:28:19 PM »
Yeah, no worries: I was missing the concept of creating new tables on the root (e.g., ::Anchor <- {..}), so that was definitely  helpful.

I still want to investigate animation. Nothing that hasn't already been done, but knowing how it's been done should help should I want to venture into deeper waters in the future! :)

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #21 on: November 16, 2015, 04:40:17 PM »
I'm sure I have some of the math wrong, probably for wide textures but working to fix that.. here's a couple mockups layouts I did using FillArt:

https://streamable.com/ov10

https://streamable.com/p14r

https://streamable.com/gvmi

Ignore the choppy animation, as that has to do with AM running in software mode on this machine. Notice in the last one you don't actually have to fill it and can set the scale smaller and it still applies the anchor - in that one it is anchored to Top.

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 #22 on: November 16, 2015, 07:59:04 PM »
That is extremely cool!

I reworked some of what I'd been working on, just because, to get the fill working across different widths/height. I ended up running two conditionals: one to initially apply the resize, and a second to "re" resize if there was still some underlap. I had a few snaps that were still not filling, but they are now. I'm sure there's better math, but it's working. Nothing as cool as the animation though! :)

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 #23 on: November 19, 2015, 09:01:29 AM »
All right, another conundrum!

I'm using the conveyor module because I want to animate a SimpleArtStrip moving left to right. While it works as it should when used as-is, I'm trying to incorporate the FillArt module rather than generic, uncorrected aspect ratio add_artwork objects.

I'd be happy with rewriting my simplified version of the FillArt module, but I'm not sure how. Here's where I'm stuck. If I take something like the following:

Code: [Select]
local my_surface=fe.add_surface(100, 100);
local my_image=my_surface.add_image("imagepath.png");

would my_image hold any reference to my_surface? Is there any kind of "parent" attribute or similar? In my current implementation, the FillArt class allows you to pass a reference to the surface to which you'd like to apply it (defaults to ::fe). Now, I'd like to fix it so I could do something like the following:

Code: [Select]
local my_surface=fe.add_surface(100, 100);
local my_filled_art=my_surface.add_image(FillArt("snap", 0, 0, 100, 100));

and have the FillArt class obtain "parent" info from its instantiation (in this case, "my_surface"). Can this be done? Even better would be to enable something that let me do this:

Code: [Select]
// Assume my_surface as above
local my_filled_art=my_surface.add_filled_art("snap", 0, 0, 100, 100);

but I'm not sure if the overarching fe object could be appended as such.


Any thoughts? Thanks!

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #24 on: November 19, 2015, 11:06:45 AM »
That's exactly what I did with my FillArt implementation. There is no reference to the parent surface from surface.add_xxx functions so you can do one of two things... use the my_surface reference you already created or do what I did - write a class which keeps a variable called 'parent' that references the surface like so:

Code: [Select]
class MyObject
{
   parent = null;
   art = null;
   constructor( name, x, y, w, h )
   {
      parent = ::fe.add_surface(w,h);
      parent.x = x;
      parent.y = y;
      art = parent.add_artwork( name, 0, 0, parent.width, parent.height );
   }
}

Now this class has reference to both the parent object (the surface) and the artwork:
Code: [Select]
local obj = MyObject( "snap", 50, 50, 320, 240 );
local parent = obj.parent;
local art = obj.art;

Without creating 'redirection' _get and _set functions though like I did in FillArt, you will have to use those references to modifying the settings - in other words, obj.x/.y/.width/.height don't exist in the class - you'd have to use obj.parent.x/.y/.w/.h to change the surface properties, or obj.art.x/.y/.width/.height to change the artwork (on the surface) properties.
« Last Edit: November 19, 2015, 11:10:52 AM 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 #25 on: November 19, 2015, 11:32:03 AM »
Okay, I think I can wrap my head around that... :) Is the posted version of your FillArt module up-to-date? Or is there a more complete version kicking around on your hard drive? And if so, might I take a peek at it? Thanks!

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 #26 on: November 19, 2015, 01:17:58 PM »
Okay, I can't wrap my head around that. I'm not sure why Squirrel is proving so difficult for me to comprehend! :(

Have you given any thought to integrating your FillArt module with the Conveyor/SimpleArtStrip? All of my attempts end in tears and ashes :(

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: preserve_aspect_ratio, but "fill" an add_surface or add_artwork?
« Reply #27 on: November 19, 2015, 03:49:46 PM »
Classes act like most languages - I'd recommend learning a bit about how they work but let me try to give a little help:

You define classes to describe how an object will work - what it's properties are and what "functions" it can perform. Then, when you use the class you "instantiate" an object - make an instance of it. This is just what fe.add_artwork, fe.add_image, etc do - when you add those lines in your code, you are creating an instance of the artwork class, or image class, etc..

Code: [Select]
local my_image = fe.add_image( "snap", 0, 0, 320, 240 );

Since my_image is now an instance of the image class - you have access to all its defined properties and functions - .x, .y, .set_rgb(), etc.

What I'm describing here is creating your own class but it will have references to other AM objects. We're basically wrapping multiple objects into our own class. The "constructor" is essentially a function that runs when you create the instance. So look a this example:

Code: [Select]
class RedImage
{
   image = null;
   constructor( name, x, y, w, h)
   {
      image = fe.add_image( name, x, y, w, h );
      red();
   }
   function red()
   {
      image.set_rgb( 255, 0, 0 );
   }
}

local red_image = RedImage( "test.png", 0, 0, 320, 240 );

So, we created an instance of 'RedImage', the constructor runs - which sets the 'image' variable to an instance of fe.add_image, then the 'red' function is called which sets the rgb of the image to red.

In your case, you want to work with a surface and objects on the surface, so the class might look like this:

Code: [Select]
class MySurface
{
   parent = null;
   art = null;
   constructor(x, y, w, h)
   {
      parent = fe.add_surface(w, h);
      art = parent.add_artwork("snap", 0, 0, w, h );
   }
   function do_something()
   {
      print("I did something\n");
   }
}

local obj = MySurface(0, 0, 320, 240 );

Now the constructor runs - creates a surface (parent) and adds a snap artwork (art) to the instance. We can now use our defined instance 'obj' to access those, or run any functions we define, or variables that are defined within the class.

It's pretty easy to modify the SimpleArtStrip/SimpleArtStripSlot to use something other than the artwork, but I think it'd be better for you to learn that :)

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 #28 on: November 19, 2015, 05:14:22 PM »
Thanks for the lesson :) I know how classes work, favoring composition over inheritance in most cases, etc., but I know it from an ECMA perspective (specifically ActionScript), so the syntactical differences are what are tripping me up.

Do you know if there's a good resource learning Squirrel with some actually useful examples? I mean I've looked through the reference docs, but they're extremely sparse, to the point of being of no use to someone who doesn't understand the lexical peculiarities. Thanks!

Edit: One example: if I were writing an accessor or mutator function in ActionScript, I'd have something like this:

Code: [Select]
// pseudocode
class ClassName
{
    _parent=new Sprite();
    _art=new Sprite();

    // constructor
    ClassName () {}

    function get art () { return _art; }
    function set art (val) { _art=val; }
}

I see there are metamethods (_get and _set) in Squirrel, but they don't work that way, and blah blah blah... :(

Again, thanks for any insight!
« Last Edit: November 19, 2015, 05:20:58 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 #29 on: November 19, 2015, 06:58:35 PM »
Yes the reference docs are very... literal :) Definitely check out this:
https://electricimp.com/docs/squirrel/squirrelcrib/

Also, I started a brief intro to squirrel and layouts here:
https://github.com/mickelson/attract/wiki/Introduction-to-Squirrel-Programming

I haven't gotten around to things like classes in that though.

Code: [Select]
class ClassName
{
   _parent = null;
   _art = null;
   constructor()
   {
      _parent = Sprite();
      _art = Sprite();
   }   
}
Two main differences here.

Notice I set to null, then initialized in the constructor - this is because variables that are not integer, bool or string are shared between class instances if they are initialized in the class (as in your example). Doing this forces the variable to be unique to each instance.

get and sets are not necessary. As far as I know there is no private variables. You could still do functions for getting and setting if it makes sense for the class. There is however a backend, hidden get and set for variables of the class which is the _get and _set I use in FillArt. Those are run whever some attempts to get or set a variables for the class. If you did my_class.something = 10 - the _set function is called and you could just allow it to be set, or override the behavior.

Hope this helps! I can probably put together a SimpleFillArtStrip / SimpleFillArtStripSlot sometime tomorrow. I'd recommend trying to look at the example in conveyor and trying to figure out how to do it for yourself though.