• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[OTClient][Mehah] aPNG - num_plays handler & animatedTexture synchronous when plays once!

Fresh

Quack!
Joined
Oct 21, 2009
Messages
1,837
Solutions
18
Reaction score
614
Location
Poland
Hello OTLanders!
Everyday im discovering and trying to use OTClient even better (still learning)
This "simple" fix (took about ~1h to investigate and repair it to be working as intended), so if you gonna use this fix - please leave a simple LIKE. Thanks!​

Explanation:
So far I saw .aPNG files to be used in OTC on different servers only for cool looking animated background at Login Screen, why not to use them to even more stuff, for example - effects on widgets?
I was messed around, tested several ways how it can be used and there's my usage of .aPNG (simple effects, for example in my module : 🎥 Watch simple usage - Preview!)

However I'm not gonna release to everyone my custom module, I just extended the .aPNG handler to be working like intended and want to share the solution.

You asking what can be wrong and about what is this thread?
Let me explain what is wrong with default OTC animatedTexture's (.aPNG) :
- First, basically - .aPNG - file structure contains few variables inside file, important of them are : first frame, total frames, delayBetweenFrames and ofcourse (that one which isn't working, that's why I created this thread) : num_frames (Loops Counter), that handles how many times .aPNG should be played/displayed on screen until it should stop.
For example: If you set Loops to 0, it should be playing infinite times (loop), if you set it to 1 or 2 or even more - it should be played those amount of times that u set in this variable, but.. default OTClient doesn't read this variable.
So - this thread is mostly about adding possibility to read - num_frames (Loops) correctly from .aPNG file - and - handling it by OTClient.

Let's start with editing sources of client (C++ part):

/framework/graphics/texture.h , in public:​
C++:
virtual void startAnimation() { return; }

/framework/graphics/animatedtexture.h , in public:​
C++:
void startAnimation();
/framework/graphics/animatedtexture.cpp , create function startAnimation() which will simply start the animation when it's necessary (that will be used in setImageSource later, it's for "refresh" the .aPNG and make it appear again when it's necessary (by default, it will be playing num_frames times and stops (and client will not re-appear this animation automatically, so that's why we want to force it to appear by this function)​
C++:
void AnimatedTexture::startAnimation()
{
        m_animTimer.restart();
        m_currentFrame = 0;
        m_id = m_frames[m_currentFrame]->getId();
}
/framework/graphics/animatedtexture.cpp - change existing function - AnimatedTexture::updateAnimation()
C++:
void AnimatedTexture::updateAnimation()
{
    if(m_animTimer.ticksElapsed() < m_framesDelay[m_currentFrame])
        return;

    if(!m_animTimer.running())
        return;

    m_animTimer.restart();
    m_currentFrame++;
    if(m_currentFrame >= m_frames.size()){
        if(m_repeat == false)
            m_animTimer.stop();
        m_currentFrame = 0;
    }

    m_id = m_frames[m_currentFrame]->getId();
}
/framework/graphics/animatedtexture.cpp - change existing function - AnimatedTexture::setRepeat(bool repeat)
C++:
void AnimatedTexture::setRepeat(bool repeat)
{
    for(const TexturePtr& frame : m_frames)
        frame->setRepeat(repeat);

    m_repeat = repeat;
    if(m_repeat){
        m_animTimer.restart();
        m_currentFrame = 0;
        m_id = m_frames[m_currentFrame]->getId();
    }
}
Now, in AnimatedTexture::AnimatedTexture , at the end of this function in default variables, for example after: m_animTimer.restart(); add:​
C++:
m_repeat = true;
(Defaults .aPNG will be looped automatically if num_plays of rendered by any .aPNG software program (Loops/num_plays) be 0 (loop infinite) (always)

framework/ui/uiwidgetimage.cpp (it's splitted into few different files (.cpp) inside Mehah's repository (that client im using), in default OTClient u will have something thats looks similar inside the uiwidget.cpp I guess, Mehah's just splitted images/text/and something else into different files, just find similar lines to those inside your sources and change it:​
C++:
void UIWidget::setImageSource(const std::string& source)
{
    if(source.empty()) {
        m_imageTexture = nullptr;
    } else {
        m_imageTexture = g_textures.getTexture(source);
        // animatedPNG handler (.aPNG)
        if (m_imageTexture->isAnimatedTexture()) {
            m_imageTexture->startAnimation();
        }
    }

    if(m_imageTexture && (!m_rect.isValid() || m_imageAutoResize)) {
        Size size = getSize();
        const Size imageSize = m_imageTexture->getSize();
        if(size.width() <= 0 || m_imageAutoResize)
            size.setWidth(imageSize.width());
        if(size.height() <= 0 || m_imageAutoResize)
            size.setHeight(imageSize.height());
        setSize(size);
    }

    m_imageMustRecache = true;
}
framework/graphics/texturemanager.cpp , there is huge edit because it's now reading num_plays (Loops count) from .aPNG file, so read carefully and see the changes:
C++:
TexturePtr TextureManager::loadTexture(std::stringstream& file)
{
    TexturePtr texture;

    apng_data apng;
    if(load_apng(file, &apng) == 0) {
        const Size imageSize(apng.width, apng.height);
        if(apng.num_frames > 1) { // animated texture
      
            std::vector<ImagePtr> frames;
            std::vector<int> framesDelay;

            if(apng.num_plays > 1){
                for(uint j = 1; j <= apng.num_plays; j++){
                    for(uint i = 0; i < apng.num_frames; ++i) {
                        uchar* frameData = apng.pdata + ((apng.first_frame + i) * imageSize.area() * apng.bpp);
                        int frameDelay = apng.frames_delay[i];

                        framesDelay.push_back(frameDelay);
                        frames.push_back(ImagePtr(new Image(imageSize, apng.bpp, frameData)));
                    }
                }
            } else {
                for(uint i = 0; i < apng.num_frames; ++i) {
                    uchar* frameData = apng.pdata + ((apng.first_frame + i) * imageSize.area() * apng.bpp);
                    int frameDelay = apng.frames_delay[i];

                    framesDelay.push_back(frameDelay);
                    frames.push_back(ImagePtr(new Image(imageSize, apng.bpp, frameData)));
                }
            }
            const AnimatedTexturePtr animatedTexture = new AnimatedTexture(imageSize, frames, framesDelay);
            if(apng.num_plays > 0)
                animatedTexture->setRepeat(false);
       
            m_animatedTextures.push_back(animatedTexture);
            texture = animatedTexture;
        } else {
            const auto image = ImagePtr(new Image(imageSize, apng.bpp, apng.pdata));
            texture = TexturePtr(new Texture(image));
        }
        free_apng(&apng);
    }

    return texture;
}

That's pretty much all!
(If I don't forgot about something, if I forgot - please make a post and I will try to edit this thread)

Now, how to test it?
1) Render properly .aPNG file using any software that can do it (I tested on it - eZGif aPNG Maker), set Loops on this website to: 1 (that will MUST be played once after my sources edits (after play Loops time, it will stop (if you not use setImageSource or destroy widget contains that animation), tested and it's works as it should).
2) Create any simple module where u can execute this .aPNG and destroy widget and setImageSource (that's now is working like - if texture is animatedTexture it's executing startAnimation())
3) Use my part of function code where u can test this (in comments i left explains if you are novice):
Lua:
function showPNGeffect()
    local pngEffect= g_ui.createWidget('UIWindow', effectsContainer) -- effectsContainer should be an previous setted widget (in .otui or wherever) where u can attach this effect (place to display this dynamically created .aPNG file)
    pngEffect:setSize({height = H, width = W}) -- change H,W variables to your .aPNG file dimensions (width x height)
    pngEffect:fill('effectsContainer') -- set anchors to the effectsContainer
    pngEffect:setImageSource('effects/generated.png') -- path to load your generated .aPNG file
-- if you done c++ changes properly, it should startAnimation if even was only 1 loop in .aPNG file structure
-- it's just to force starting animation AGAIN because it will always stops after doing 1 or X loops that u set when creating(rendering by software program) .aPNG.
 
-- scheduleEvent to destroy generated pngEffect, i don't know if its necesarry i always like to destroy dynamic generated widgets for safety reasons
scheduleEvent(function()
        pngEffect:destroy()
    end, 5000) -- time (in ms) when destroy this generated widget (pngEffect)
end
4) Execute this function (from previous step) in any button, just think where u can place it, lol, if you wish to use this fix u should know what are you doing in OTC.

Enjoy!
If you have any questions - or - different way to achieve this num_plays read and do the thing with plays only X num_plays times from .aPNG file structure,
Feel free to share your method in comment below.

Have a good day/night,
Fresh.
 
Last edited:
Thanks for sharing! Your widget looks pretty dope with the maplestory scroll functionality. Did you created a PR in the project also?
 
Thanks for sharing! Your widget looks pretty dope with the maplestory scroll functionality. Did you created a PR in the project also?
Nope I didn't, you can create it and include my "credits", im not using git too much, just doing everything hobbystic on my local PC, back to the MapleStory - yeah the whole scroll system (not this UI), but functionality (server-side) and even "remaining slots attempt" I recreated for myself from MS, nostalgic game for me ;) Sprites will be changed someday 🤭
 
This is hella fresh, and dope af! Thanks for sharing with us man, much appreciated!
 
Thanks for this contribution Fresh in my case these changes produce an unknown bug as the normal png does not render correctly. No logs, no bugs, just doesn't show it.
 
Thanks for this contribution Fresh in my case these changes produce an unknown bug as the normal png does not render correctly. No logs, no bugs, just doesn't show it.
Strange, I just added one variable from a aPNG structure to be read by the client without changing any logic from original code. Are you sure you done everything correctly?
 
Back
Top