RMMV How to properly load and show pictures in a Window???

Hisao Shou

Veteran
Veteran
Joined
Jan 8, 2015
Messages
181
Reaction score
26
Primarily Uses
Hello there,
I've been recently trying a lot to have a window displayed and display several images inside that window, but no matter how much I beat my head I can't figure out why the pictures will not properly load since first attempts...

And even worse, I have to attempt to call the images several times in order to display them, sometimes on mobile it does not even load at all when calling the window for the first time, despite having that timedout.

JavaScript:
////////////////////////////
// This is just 90% of the script to present it logic regarding updating and displaying images
////////////////////////////


function Scene_ImgCreation() {
    this.initialize.apply(this, arguments);
}

Scene_ImgCreation.prototype = Object.create(Scene_MenuBase.prototype);
Scene_ImgCreation.prototype.constructor = Scene_ImgCreation;

Scene_ImgCreation.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};

Scene_ImgCreation.prototype.create = function() {
    Scene_MenuBase.prototype.create.call(this);
    var ww = Graphics.boxWidth;
    this._myWindow = new Window_ImageContainer(ww - 360, Graphics.boxHeight - 162, 360, 162);
    var wy = this._myWindow.height;
    var wh = Graphics.boxHeight - wy;
    ImageManager.reservePicture("test/placeholderImg"); // Attempt to preload it
    this.addWindow(this._myWindow);
};



// Start creating the window itself
Window_ImageContainer.prototype = Object.create(Window_Base.prototype);
Window_ImageContainer.prototype.constructor = Window_ImageContainer;


Window_ImageContainer.prototype.initialize = function(x, y, width, height) {
    Window_Base.prototype.initialize.call(this, x, y, width, height);
    this.needRefresh = true;
    
    // This first time will never dispaly the image, but properly starts loading it to make the ImageManager busy
    this.drawPicture("test/placeholderImg", 10, 10);

    setTimeout(this.timedOut.bind(this), 300); // I need a timedout to refresh again after X frames so we don't refresh it every frame
    this.refresh();


};


Window_ImageContainer.prototype.timedOut = function() {
    this.refresh();
}


Window_ImageContainer.prototype.refresh = function() {
    this.contents.clear();
    
    if (ImageManager.isReady() == true) {
        this.drawPicture("test/placeholderImg", 10, 10); // Barely now the image gets displayed
        this.needRefresh = false;
    }       
}



Window_ImageContainer.prototype.drawPicture = function(filename, x, y) {   
    var bitmap = ImageManager.loadPicture(filename);   
    this.contents.blt(bitmap, 0, 0, bitmap._canvas.width, bitmap._canvas.height, x, y); 
};       



Window_ImageContainer.prototype.update = function() {
    Window_Base.prototype.update.call(this);
    if (!ImageManager.isReady()) {
        this.needRefresh = false;
        this.refresh();
    }
    
    if (ImageManager.isReady() && this.needRefresh == true) {
        this.refresh();
    }
    
};


Could someone please tell me what am I doing wrong?
 

caethyril

^_^
Global Mod
Joined
Feb 21, 2018
Messages
2,957
Reaction score
2,292
First Language
EN
Primarily Uses
RMMZ
The engine loads (and reserves) resources asynchronously, but you're trying to draw the image immediately. If it is not already in memory, i.e. in the game's image cache, then the draw attempt will fail. I think this is what you're seeing.

The scene will initialize on scene change. Only once ImageManager.isReady() returns true will the scene's start method be called:
JavaScript:
SceneManager.updateScene = function() {
    if (this._scene) {
        if (!this._sceneStarted && this._scene.isReady()) {
            this._scene.start();
// ...
Scene_Menu, for example, uses this to wait until the party's face images are loaded before refreshing its status window:
JavaScript:
Scene_Menu.prototype.start = function() {
    Scene_MenuBase.prototype.start.call(this);
    this._statusWindow.refresh();
};
You could do something similar with your scene to solve problems on scene start.

To ensure it always waits for the resources to be loaded before painting, you could use one of the following approaches:
  1. Don't use Bitmap#blt: instead add a Sprite object to the window background and tell it to load the appropriate bitmap, e.g.
    JavaScript:
    Window_ImageContainer.prototype.initialize = function(x, y, width, height) {
      Window_Base.prototype.initialize.apply(this, arguments);
      // create new Sprite with bitmap img/pictures/test.png
      this._image1 = new Sprite();
      this._image1._bitmap = ImageManager.loadPicture("test");
      // add new Sprite as a child to the window's background
      this.contents.addChild(this._image1);
    };
    Sprites handle their own refreshes, so this is probably the simplest option! :kaohi:

  2. Alternatively, invoke the draw call via a load listener, e.g.
    JavaScript:
    // Context: paint bmp to this window at screen coords x, y (px)
    var onLoadImage = function(x, y, bmp) {
      this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
    };
    
    Scene_ImgCreation.prototype.initialize = function() {
      // initiate async load request for img/pictures/test.png
      var bmp = ImageManager.loadBitmap('img/pictures/', 'test');
      // assign function to run when the bitmap loads, including x & y coords
      bmp.addLoadListener(onLoadImage.bind(this._myWindow, 20, 40));
    };
    Load listeners should be destroyed with their parent, so I think this is safe without any additional checks in the onLoadImage function, even if the user goes to another scene before the image loads.
For more details on RMMV's Sprite and/or Bitmap wrappers (including load listeners), check out rpg_core.js.

Other notes:
  • Bitmap#blt is usually for painting parts of one bitmap to another bitmap, and is more CPU-heavy than just showing the loaded image as-is.

  • If you want to resize a window after adding it to the scene, remember to invoke the window's createContents method afterwards, e.g.
    JavaScript:
        this._myWindow = new Window_ImageContainer(ww - 360, Graphics.boxHeight - 162, 360, 162);
        this._myWindow.height = this._myWindow.fittingHeight(5);
        this._myWindow.createContents();
        this.addWindow(this._myWindow);

  • ImageManager.isReady() checks for any outstanding image requests. You can check individual bitmaps using bitmap.isReady(), where bitmap points to the bitmap in question.

  • RMMV's default image cache size is fairly small (10 MPx) due to mobile considerations. If a new image is requested and there is not enough space in the cache for it, cache entries are removed starting with the least recent one until there is enough space. You can enlarge this using a plugin like Community_Basic (included in new projects by default) if your project is particularly image-heavy.
 

Hisao Shou

Veteran
Veteran
Joined
Jan 8, 2015
Messages
181
Reaction score
26
Primarily Uses
The engine loads (and reserves) resources asynchronously, but you're trying to draw the image immediately. If it is not already in memory, i.e. in the game's image cache, then the draw attempt will fail. I think this is what you're seeing.

The scene will initialize on scene change. Only once ImageManager.isReady() returns true will the scene's start method be called:
JavaScript:
SceneManager.updateScene = function() {
    if (this._scene) {
        if (!this._sceneStarted && this._scene.isReady()) {
            this._scene.start();
// ...
Scene_Menu, for example, uses this to wait until the party's face images are loaded before refreshing its status window:
JavaScript:
Scene_Menu.prototype.start = function() {
    Scene_MenuBase.prototype.start.call(this);
    this._statusWindow.refresh();
};
You could do something similar with your scene to solve problems on scene start.

To ensure it always waits for the resources to be loaded before painting, you could use one of the following approaches:
  1. Don't use Bitmap#blt: instead add a Sprite object to the window background and tell it to load the appropriate bitmap, e.g.
    JavaScript:
    Window_ImageContainer.prototype.initialize = function(x, y, width, height) {
      Window_Base.prototype.initialize.apply(this, arguments);
      // create new Sprite with bitmap img/pictures/test.png
      this._image1 = new Sprite();
      this._image1._bitmap = ImageManager.loadPicture("test");
      // add new Sprite as a child to the window's background
      this.contents.addChild(this._image1);
    };
    Sprites handle their own refreshes, so this is probably the simplest option! :kaohi:

  2. Alternatively, invoke the draw call via a load listener, e.g.
    JavaScript:
    // Context: paint bmp to this window at screen coords x, y (px)
    var onLoadImage = function(x, y, bmp) {
      this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
    };
    
    Scene_ImgCreation.prototype.initialize = function() {
      // initiate async load request for img/pictures/test.png
      var bmp = ImageManager.loadBitmap('img/pictures/', 'test');
      // assign function to run when the bitmap loads, including x & y coords
      bmp.addLoadListener(onLoadImage.bind(this._myWindow, 20, 40));
    };
    Load listeners should be destroyed with their parent, so I think this is safe without any additional checks in the onLoadImage function, even if the user goes to another scene before the image loads.
For more details on RMMV's Sprite and/or Bitmap wrappers (including load listeners), check out rpg_core.js.

Other notes:
  • Bitmap#blt is usually for painting parts of one bitmap to another bitmap, and is more CPU-heavy than just showing the loaded image as-is.

  • If you want to resize a window after adding it to the scene, remember to invoke the window's createContents method afterwards, e.g.
    JavaScript:
        this._myWindow = new Window_ImageContainer(ww - 360, Graphics.boxHeight - 162, 360, 162);
        this._myWindow.height = this._myWindow.fittingHeight(5);
        this._myWindow.createContents();
        this.addWindow(this._myWindow);

  • ImageManager.isReady() checks for any outstanding image requests. You can check individual bitmaps using bitmap.isReady(), where bitmap points to the bitmap in question.

  • RMMV's default image cache size is fairly small (10 MPx) due to mobile considerations. If a new image is requested and there is not enough space in the cache for it, cache entries are removed starting with the least recent one until there is enough space. You can enlarge this using a plugin like Community_Basic (included in new projects by default) if your project is particularly image-heavy.

Thank you a lot for your answer and for your time! It means a lot, I've got a much better understanding how the engine works.
Yet I still have some questions...
For example, I have to load a pretty big number of images in a window, if I take the Sprite approach, is there any way I could display all pictures in a similar manner I did with the drawPicture function ? Or do I have to insert each picture manually?

Also, since I use multiple pictures (lets say over 20), will not adding over 20 new children (Sprites) to a window cause lag or have cache issues? I'm not sure how this works, but I might be wrong, what made me ask this question was the fact sometimes when I remove certain children out of SceneManager._scene, makes the game run smoother. It might not work the same for Sprites, I don't know that's why I have asked



Regarding the load listener method, for me it seems like it does not work properly, I think I have placed something wrong...
Whenever I try to run this method I get "Cannot read property 'blt' of undefined" error, despite the fact upon console.log(bmp) inside your function clearly shows that it can read the bitmap.
I think I might have understood wrong the way these scripts should be used? For example, I don't have an initialize function, I start directly with the start scene (it runs in battle). When I tried to add an Initialize function same error occurred.
I'm not sure how this line works more precisely:
JavaScript:
bmp.addLoadListener(onLoadImage.bind(this._myWindow, 20, 40));

So the listener waits for the bitmap in addLoadListener to properly load, but also it takes 3 arguments: the bitmap itself and coordinates. I'm a little confused here, because the bitmap is declared at the start of the line, and the coordinates at the end? I'm also not sure if "this._myWindow" should point where the image to be rendered in which window, or if this creates the window itself
 

caethyril

^_^
Global Mod
Joined
Feb 21, 2018
Messages
2,957
Reaction score
2,292
First Language
EN
Primarily Uses
RMMZ
I have to load a pretty big number of images in a window, if I take the Sprite approach, is there any way I could display all pictures in a similar manner I did with the drawPicture function ? Or do I have to insert each picture manually?
For each image, you can make a new Sprite at a different position, e.g.
JavaScript:
this._images = [];
this._images[0] = new Sprite();
this._images[0]._bitmap = ImageManager.loadPicture("item-empty");
this._images[0].x = 20; this._images[0].y = 20;
this._images[1] = new Sprite();
this._images[1]._bitmap = ImageManager.loadPicture("item-empty");
this._images[1].x = 80; this._images[1].y = 20;
// etc...
for (var sprite of this._images) { this.addChild(sprite); }
You could use a loop for the bitmap/position assignment, too. If you want to make a selectable grid of images, I recommend inheriting from Window_Selectable instead: as the name suggests, it's made for that kind of thing! In that case you'd simply write the window's drawItem method to determine how each image is drawn, instead of doing it all in the refresh method. (The core selectable windows draw/paint their items to the window rather than using child sprites.)


Also, since I use multiple pictures (lets say over 20), will not adding over 20 new children (Sprites) to a window cause lag or have cache issues?
Each Sprite has a RAM overhead (and a little bit of CPU too) because it's a separate object. I'm not sure whether 20 Sprites would have a noticeable impact on framerate. Having them as separate sprites may also affect certain display aspects: you can control their positions etc manually at any point, but I think they will display on top of the existing window content, e.g. text.

RMMV's image cache is a completely separate concept: it just keeps a reference to recently-requested images. It improves load times of frequently-requested images at the expense of a bit more RAM usage. If an image is assigned to a bitmap in-game, deleting the corresponding reference from cache will not delete the bitmap itself, because JavaScript uses implicit memory management: only when all references to an object (e.g. bitmap) are deleted will that object be erased from memory, via the garbage collection routine. Technical details here:


Regarding the load listener method, for me it seems like it does not work properly, I think I have placed something wrong...
Whenever I try to run this method I get "Cannot read property 'blt' of undefined" error, despite the fact upon console.log(bmp) inside your function clearly shows that it can read the bitmap. I think I might have understood wrong the way these scripts should be used? For example, I don't have an initialize function, I start directly with the start scene (it runs in battle).
(I realise I wrote that example as a scene initialize method, which is poor design...it should be in the window's refresh method, right? Sorry for any confusion! :kaoslp:)

In my example, it blits bmp, the loaded bitmap, onto this.contents, the window contents bitmap. The error you are seeing is saying that this.contents is undefined. this is context-dependent; I guess you tried to use the example as-is in a Window_ImageContainer method? Try this instead:
JavaScript:
Window_ImageContainer.prototype.drawPicture = function(x, y, bmp) {   
  this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
};

Window_ImageContainer.prototype.refresh = function() {
  // initiate async load request for img/pictures/test.png
  var bmp = ImageManager.loadPicture("test");
  // assign function to run when the bitmap loads, including x & y coords
  bmp.addLoadListener(this.drawPicture.bind(this, 20, 40));
  // etc for other images~
};
Again, a loop could be used here to neatly update all images. :kaophew:

:kaoswt2: Also, hold up...
...I start directly with the start scene (it runs in battle).
You showed a Scene_ImgCreation scene in your code. I don't think that the core scripts are designed to handle scene changes mid-battle. You could either add some code to allow scene changes (not sure how involved that would be) or simply add your window to the battle scene and hide/show it at the appropriate times.


I'm not sure how this line works more precisely:
JavaScript:
bmp.addLoadListener(onLoadImage.bind(this._myWindow, 20, 40));
This says "when the bitmap loads, run the onLoadImage function in the context of this._myWindow, with 2 arguments: 20 and 40". "Context" determines the this-value within the function. In this case, onLoadImage expects to be run in a Window_ImageContainer context, so this should point to an existing instance of such a window. More information on this and Function#bind here:
When the bitmap load listener is called, another argument will be provided: the bitmap that has just been loaded. This helps to explain the example onLoadImage function:
JavaScript:
var onLoadImage = function(x, y, bmp) {
  this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
};
It gets the contents property of this, the bound context (i.e. the window) and blits the entire loaded bitmap, bmp, to it at the bound coordinates. If the bitmap is already in memory when the load listener is added, the listener should trigger immediately.


I'm also not sure if "this._myWindow" should point where the image to be rendered in which window, or if this creates the window itself
this and this._myWindow are just references, the window should already be defined. As mentioned above, the first argument of bind is the context for the function, which in this case should be the target game window, i.e. an instance of Window_ImageContainer.
 

Hisao Shou

Veteran
Veteran
Joined
Jan 8, 2015
Messages
181
Reaction score
26
Primarily Uses
For each image, you can make a new Sprite at a different position, e.g.
JavaScript:
this._images = [];
this._images[0] = new Sprite();
this._images[0]._bitmap = ImageManager.loadPicture("item-empty");
this._images[0].x = 20; this._images[0].y = 20;
this._images[1] = new Sprite();
this._images[1]._bitmap = ImageManager.loadPicture("item-empty");
this._images[1].x = 80; this._images[1].y = 20;
// etc...
for (var sprite of this._images) { this.addChild(sprite); }
You could use a loop for the bitmap/position assignment, too. If you want to make a selectable grid of images, I recommend inheriting from Window_Selectable instead: as the name suggests, it's made for that kind of thing! In that case you'd simply write the window's drawItem method to determine how each image is drawn, instead of doing it all in the refresh method. (The core selectable windows draw/paint their items to the window rather than using child sprites.)



Each Sprite has a RAM overhead (and a little bit of CPU too) because it's a separate object. I'm not sure whether 20 Sprites would have a noticeable impact on framerate. Having them as separate sprites may also affect certain display aspects: you can control their positions etc manually at any point, but I think they will display on top of the existing window content, e.g. text.

RMMV's image cache is a completely separate concept: it just keeps a reference to recently-requested images. It improves load times of frequently-requested images at the expense of a bit more RAM usage. If an image is assigned to a bitmap in-game, deleting the corresponding reference from cache will not delete the bitmap itself, because JavaScript uses implicit memory management: only when all references to an object (e.g. bitmap) are deleted will that object be erased from memory, via the garbage collection routine. Technical details here:



(I realise I wrote that example as a scene initialize method, which is poor design...it should be in the window's refresh method, right? Sorry for any confusion! :kaoslp:)

In my example, it blits bmp, the loaded bitmap, onto this.contents, the window contents bitmap. The error you are seeing is saying that this.contents is undefined. this is context-dependent; I guess you tried to use the example as-is in a Window_ImageContainer method? Try this instead:
JavaScript:
Window_ImageContainer.prototype.drawPicture = function(x, y, bmp) {  
  this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
};

Window_ImageContainer.prototype.refresh = function() {
  // initiate async load request for img/pictures/test.png
  var bmp = ImageManager.loadPicture("test");
  // assign function to run when the bitmap loads, including x & y coords
  bmp.addLoadListener(this.drawPicture.bind(this, 20, 40));
  // etc for other images~
};
Again, a loop could be used here to neatly update all images. :kaophew:

:kaoswt2: Also, hold up...

You showed a Scene_ImgCreation scene in your code. I don't think that the core scripts are designed to handle scene changes mid-battle. You could either add some code to allow scene changes (not sure how involved that would be) or simply add your window to the battle scene and hide/show it at the appropriate times.



This says "when the bitmap loads, run the onLoadImage function in the context of this._myWindow, with 2 arguments: 20 and 40". "Context" determines the this-value within the function. In this case, onLoadImage expects to be run in a Window_ImageContainer context, so this should point to an existing instance of such a window. More information on this and Function#bind here:
When the bitmap load listener is called, another argument will be provided: the bitmap that has just been loaded. This helps to explain the example onLoadImage function:
JavaScript:
var onLoadImage = function(x, y, bmp) {
  this.contents.blt(bmp, 0, 0, bmp._canvas.width, bmp._canvas.height, x, y);
};
It gets the contents property of this, the bound context (i.e. the window) and blits the entire loaded bitmap, bmp, to it at the bound coordinates. If the bitmap is already in memory when the load listener is added, the listener should trigger immediately.



this and this._myWindow are just references, the window should already be defined. As mentioned above, the first argument of bind is the context for the function, which in this case should be the target game window, i.e. an instance of Window_ImageContainer.
Alright this finally worked, thank you! It means a lot!

I have managed to display the images properly on load. Yet, a small issue I have encountered with the "bmp.addLoadListener(this.drawPicture.bind(this, 20, 40));" method, basically when the image is loaded a function is called to get the image displayed, the problem is that this might not happen always instantaneously and this is fine, the problem comes when you need to draw a text over the image.
Because the image might load slower, the text which is supposed to be drawn after the image gets drawn behind the image since it was displayed after it loaded

I have tried to create a function inside the Window_ImageContainer.prototype.drawPicture to draw text each time this function is loaded, yet I ended up with much text displayed on top of each other
 

caethyril

^_^
Global Mod
Joined
Feb 21, 2018
Messages
2,957
Reaction score
2,292
First Language
EN
Primarily Uses
RMMZ
Because the image might load slower, the text which is supposed to be drawn after the image gets drawn behind the image since it was displayed after it loaded

I have tried to create a function inside the Window_ImageContainer.prototype.drawPicture to draw text each time this function is loaded, yet I ended up with much text displayed on top of each other
Try clearing the window's contents bitmap before drawing, e.g.
JavaScript:
this.contents.clear();
Alternatively you can use clearRect to clear only a specific area:
JavaScript:
this.contents.clearRect(x, y, width, height);
 

Latest Threads

Latest Posts

Latest Profile Posts

Folks, if anyone sends you a message inviting you to add their API to your game, please report them. We don't want people spamming our members asking them to add stuff to their games for their own (or others') benefit.
I'm listening to iiluminaughtii talking about the secrets behind shady businesses and scams. Meanwhile I'm writing a fanfic about Sephiroth from FF7, one specifically for my stepmom. I'll get to give it to her, too, because my boss said I could take the holidays off! Dec. 22-27 I will be in Orlando, and maybe Fort Myers too, visiting my dad and stepmom!
Of course, I bought a handful of games on the Steam Black Friday sale. Will I try them? Will I complete any? Who knows...
I don't really have any neat updates today about how I screwed up programming... But if you want to observe the intensenes of me programming, then you can check out my stream :)
New Weapons.gif
No more spam from me today, I promise! Just wanted to upload this, people have been giving me feedback that my weapon sprites (which were RTP) clashed badly with my battlers for ages, so today I finally took the plunge and updated them! Really happy with how they came out :D :D

Forum statistics

Threads
117,009
Messages
1,103,786
Members
152,904
Latest member
gumi16
Top