vinod777

[SOLUTION] Scaling for multiple devices,resolution and screens

Recommended Posts

UPDATE: Read the thread down to find posts with best solution(s);

 

There is  a lot of discussions in this forum about scaling a Phaser Game. I found most of the game devs and examples using a high quality art/assets for games. For smaller devices the canvas is scaled down accordingly. This creates a doubtful question in our minds about the performance impact of overkilling small devices with hd graphics. Well I found the first step to improve that.

 

1. First of all decide the width and height of your game world. Think of this dimension in an ideal world i.e., 1:1 pixel ratio. So for example if the width and height is 800x480, then adding a sprite on x=0 will be on left most edge and on x=800, it will be on right most edge. This is your logical game width and height.

 

2. Create the phaser game with the logical width and height. 

var gameWidth = 800;var gameHeight = 480;var game = new Phaser.Game(gameWidth, gameHeight, Phaser.AUTO, 'game');

3. Now do a show_all scaling, so that black bars will appear on non-fitting screen sides. This is added in a method called scaleStage inside the Boot state.

if (this.game.device.desktop)        {            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;            this.scale.minWidth = gameWidth/2;            this.scale.minHeight = gameHeight/2;            this.scale.maxWidth = gameWidth;            this.scale.maxHeight = gameHeight;            this.scale.pageAlignHorizontally = true;            this.scale.pageAlignVertically = true;            this.scale.setScreenSize(true);        }        else        {            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;            this.scale.minWidth = gameWidth/2;            this.scale.minHeight = gameHeight/2;            this.scale.maxWidth = 2048; //You can change this to gameWidth*2.5 if needed            this.scale.maxHeight = 1228; //Make sure these values are proportional to the gameWidth and gameHeight            this.scale.pageAlignHorizontally = true;            this.scale.pageAlignVertically = true;            this.scale.forceOrientation(true, false);            this.scale.hasResized.add(this.gameResized, this);            this.scale.enterIncorrectOrientation.add(this.enterIncorrectOrientation, this);            this.scale.leaveIncorrectOrientation.add(this.leaveIncorrectOrientation, this);            this.scale.setScreenSize(true);        }

4. Now what the above code will do is scale your game to SHOW_ALL. This will fit your game inside the screen. Black bars appears possibly. We are now going to fill the game into the screen by scaling the canvas up to the point where the black bars are removed. After the above code, add this.

var ow = parseInt(this.game.canvas.style.width,10);var oh = parseInt(this.game.canvas.style.height,10);var r = Math.max(window.innerWidth/ow,window.innerHeight/oh);var nw = ow*r;var nh = oh*r;this.game.canvas.style.width = nw+"px";this.game.canvas.style.height= nh+"px";this.game.canvas.style.marginLeft = (window.innerWidth/2 - nw/2)+"px"; this.game.canvas.style.marginTop = (window.innerHeight/2 - nh/2)+"px";document.getElementById("game").style.width = window.innerWidth+"px";document.getElementById("game").style.height = window.innerHeight-1+"px";//The css for body includes 1px top margin, I believe this is the cause for this -1document.getElementById("game").style.overflow = "hidden";

5. Now the content will be filled. You can call scaleStage on 'leaveIncorrectOrientation' method too.

 

You can decide the values for gameWidth and gameHeight according to the device resolution(not devicePixelRatio, html5!=native), load the different assets like sd/md/hd accordingly. This time, you need to think about the logical values and the loaded asset dimensions. You may want to multiply screen co-ordinates with the loaded asset scale. I will probably write a blog post on this soon.

 

Thanks for this super cool game library. :D & Thanks for reading this.

Share this post


Link to post
Share on other sites

First and foremost, thank you very much for sharing.

 

I still have some doubts. I hope you don't mind answering me :)

 

Prior to read your post I thought to do the following: given the ratio between width and height from device, I'd pick a different set of images. For instance, devices with resolutions of 320x480 and 640x960 have the same ratio (2:3) so I'd pick for both images with 640x960. Devices with resolutions of 240x320, 480x640 have the same ratio (3/4) so I'd pick for both images with 480x640, and so on.

 

Using you method, suppose I choose 800x480 resolution for the game. What will we consider sd/md/hd?

 

I mean, hd means width and height bigger than 800 and 480 respectively or if just width or height bigger than my dimensions will be considered hd (for instance, 854x480 is md or hd) ?

 

The same doubt go sd and md.

 

Moreover, in case I pick a resolution of 960x540, how will it fit in screens like 800x480?

 

You say

You may want to multiply screen co-ordinates with the loaded asset scale.

 

 

Why the may? What are the pros and cons of doing / not doing it?

 

And please, write a post about this in details and full of sample code :D

 

Regards.

Share this post


Link to post
Share on other sites

It is a great post and I vote it to be pinned. It works ok and re-size the entire canvas to the entire browser screen. But I just noticed that when I click buttons, the location of the tap should be slightly above the button for the button to do its event or what ever the button should do. Another thing is when I return to correct orientation the black border appears again.

Share this post


Link to post
Share on other sites

Well, thanks for your comments.

 

I was playing with this and found that the scaling works but it will make other things difficult while creating games. For example, that mouse click offset problem you told.

 

I believe this post clears all your doubts. I have copied some text from my blog post that I will share the link on bottom.

 

We are considering five screen sizes;

Small – 360x240
Normal – 480x320
Large – 720x480
XLarge – 960x640
XXLarge – 1440x960

 

First, we need to create assets for those screens. In this example, we are going to create a bg image to show how it will appear on all resolutions. You can use any asset resizer for this purpose, but make sure the bg dimensions are exactly the above values for each one. I used this one https://github.com/asystat/Final-Android-Resizer

 

What we are going to do is like this: for a 800x480 device, the game loads 'large' asset and uses 720x480 resolution and scales the canvas up to fill the screen.

 

We need to decide on what will be our game logic width and logic height. That is, if a sprite is positioned horizontally on logicWidth it should display on right edge. In this example, we take 480x320 as logicWidth and logicHeight respectively. Based on this aspect ratio, we will scale up/down the gameWidth and gameHeight.

In the index.html, 

(function () {	//By default we set 	BasicGame.screen = "small";	BasicGame.srx = Math.max(window.innerWidth,window.innerHeight);	BasicGame.sry = Math.min(window.innerWidth,window.innerHeight);		BasicGame.logicWidth = 480;	BasicGame.logicHeight = 320;	var r = BasicGame.logicWidth/BasicGame.logicHeight;	if(BasicGame.srx >= 360){		BasicGame.screen = "small";		BasicGame.gameWidth = 360;	}	if(BasicGame.srx >= 480){		BasicGame.screen = "normal";		BasicGame.gameWidth = 480;	}	if(BasicGame.srx >= 720){		BasicGame.screen = "large";		BasicGame.gameWidth = 720;	}	if(BasicGame.srx >= 960){		BasicGame.screen = "xlarge";		BasicGame.gameWidth = 960;	}	if(BasicGame.srx >= 1440){		BasicGame.screen = "xxlarge";		BasicGame.gameWidth = 1440;	}		//If on deskop, we may need to fix the maximum resolution instead of scaling the game to the full monitor resolution	var device = new Phaser.Device();	if(device.desktop){		BasicGame.screen = "large";		BasicGame.gameWidth = 720;	}	device = null;			BasicGame.gameHeight = BasicGame.gameWidth/r;	//We need these methods later to convert the logical game position to display position, So convertWidth(logicWidth) will be right edge for all screens	BasicGame.convertWidth = function(value){		return value/BasicGame.logicWidth * BasicGame.gameWidth; 	};	BasicGame.convertHeight = function(value){		return value/BasicGame.logicHeight * BasicGame.gameHeight;	};		var game = new Phaser.Game(BasicGame.gameWidth,BasicGame.gameHeight, Phaser.AUTO, 'game');	//	Add the States your game has.	//	You don't have to do this in the html, it could be done in your Boot state too, but for simplicity I'll keep it here.	game.state.add('Boot', BasicGame.Boot);	game.state.add('Preloader', BasicGame.Preloader);	game.state.add('MainMenu', BasicGame.MainMenu);	game.state.add('Game', BasicGame.Game);	//	Now start the Boot state.	game.state.start('Boot');})();

In the Boot.js, create a method scaleStage and add this code,

scaleStage:function(){    	if (this.game.device.desktop)        {            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;         }        else        {            this.scale.scaleMode = Phaser.ScaleManager.NO_BORDER;            this.scale.forceOrientation(true, false);            this.scale.hasResized.add(this.gameResized, this);            this.scale.enterIncorrectOrientation.add(this.enterIncorrectOrientation, this);            this.scale.leaveIncorrectOrientation.add(this.leaveIncorrectOrientation, this);            this.scale.setScreenSize(true);        }                this.scale.minWidth = BasicGame.gameWidth/2;        this.scale.minHeight = BasicGame.gameHeight/2;        this.scale.maxWidth = BasicGame.gameWidth;        this.scale.maxHeight = BasicGame.gameHeight;        this.scale.pageAlignHorizontally = true;        this.scale.pageAlignVertically = true;        this.scale.setScreenSize(true);        		if(this.scale.scaleMode==Phaser.ScaleManager.NO_BORDER){			BasicGame.viewX = (this.scale.width/2 - window.innerWidth/2)*this.scale.scaleFactor.x;			BasicGame.viewY = (this.scale.height/2 - window.innerHeight/2 - 1)*this.scale.scaleFactor.y;			BasicGame.viewWidth = BasicGame.gameWidth-BasicGame.viewX;			BasicGame.viewHeight = BasicGame.gameHeight-BasicGame.viewY;		}else{			BasicGame.viewX = 0;			BasicGame.viewY = 0;			BasicGame.viewWidth = BasicGame.gameWidth;			BasicGame.viewHeight = BasicGame.gameHeight;		}			document.getElementById("game").style.width = window.innerWidth+"px";		document.getElementById("game").style.height = window.innerHeight-1+"px";//The css for body includes 1px top margin, I believe this is the cause for this -1		document.getElementById("game").style.overflow = "hidden";    },

We set SHOW_ALL for desktop browsers and NO_BORDER for mobile devices. Of course, you can change this to your preference.

 

There are four parameters defined - viewX, viewY,viewWidth and viewHeight

 

These correspond to our viewing area. We will require this to position hud elements to the edges of the screen.

 

In the preloader, you can load assets like

this.load.image('bg','assets/'+BasicGame.screen+"/bg.this.load.image('playBtn','assets/'+BasicGame.screen+"/playBtn.png");

Before doing all this, please use this ScaleManager2.js file. I have modified the scaling method in Phaser.ScaleManager to support the NO_BORDER scaling. This ensure us that mouse input clicks are positioned well and not offsetted (This happened before during testing and hence I wrote this code).

/**Injecting no border code for Phaser.ScaleManager*/Phaser.ScaleManager.prototype.NO_BORDER = 3;Phaser.ScaleManager.prototype.setScreenSize = function (force) {        if (typeof force == 'undefined')        {            force = false;        }        if (this.game.device.iPad === false && this.game.device.webApp === false && this.game.device.desktop === false)        {            if (this.game.device.android && this.game.device.chrome === false)            {                window.scrollTo(0, 1);            }            else            {                window.scrollTo(0, 0);            }        }        this._iterations--;        if (force || window.innerHeight > this._startHeight || this._iterations < 0)        {            // Set minimum height of content to new window height            document.documentElement['style'].minHeight = window.innerHeight + 'px';            if (this.incorrectOrientation === true)            {                this.setMaximum();            }            else if (!this.isFullScreen)            {                if (this.scaleMode == Phaser.ScaleManager.EXACT_FIT)                {                    this.setExactFit();                }                else if (this.scaleMode == Phaser.ScaleManager.SHOW_ALL)                {                    this.setShowAll();                }                else if(this.scaleMode == Phaser.ScaleManager.NO_BORDER)                {                	this.setNoBorder();//Don't call setSize                	clearInterval(this._check);            		this._check = null;            		return;                }            }            else            {                if (this.fullScreenScaleMode == Phaser.ScaleManager.EXACT_FIT)                {                    this.setExactFit();                }                else if (this.fullScreenScaleMode == Phaser.ScaleManager.SHOW_ALL)                {                    this.setShowAll();                }                else if(this.scaleMode == Phaser.ScaleManager.NO_BORDER)                {                	this.setNoBorder();//Don't call setSize                	clearInterval(this._check);            		this._check = null;            		return;                }            }            this.setSize();            clearInterval(this._check);            this._check = null;        }    }Phaser.ScaleManager.prototype.setNoBorder = function(){		this.setShowAll();		var ow = parseInt(this.width,10);		var oh = parseInt(this.height,10);		var r = Math.max(window.innerWidth/ow,window.innerHeight/oh);		this.width = ow*r;		this.height = oh*r;		this.setSize2();}Phaser.ScaleManager.prototype.setSize2 = function(){        this.game.canvas.style.width = this.width + 'px';        this.game.canvas.style.height = this.height + 'px';        this.game.input.scale.setTo(this.game.width / this.width, this.game.height / this.height);        if (this.pageAlignHorizontally)        {            if (this.incorrectOrientation === false)            {                this.margin.x = Math.round((window.innerWidth - this.width) / 2);                this.game.canvas.style.marginLeft = this.margin.x + 'px';            }            else            {                this.margin.x = 0;                this.game.canvas.style.marginLeft = '0px';            }        }        if (this.pageAlignVertically)        {            if (this.incorrectOrientation === false)            {                this.margin.y = Math.round((window.innerHeight - this.height) / 2);                this.game.canvas.style.marginTop = this.margin.y + 'px';            }            else            {                this.margin.y = 0;                this.game.canvas.style.marginTop = '0px';            }        }        Phaser.Canvas.getOffset(this.game.canvas, this.game.stage.offset);        this.aspectRatio = this.width / this.height;        this.scaleFactor.x = this.game.width / this.width;        this.scaleFactor.y = this.game.height / this.height;        this.scaleFactorInversed.x = this.width / this.game.width;        this.scaleFactorInversed.y = this.height / this.game.height;        this.hasResized.dispatch(this.width, this.height);        this.checkOrientationState();}

And remember, when positioning an object, use convertWidth and convertHeight.  For HUD elements use viewX, viewY, viewWidth and viewHeight.

 

Full code example here: link

 

Link to the full blog post

 

I hope I've not made any mistakes. Thanks.

Share this post


Link to post
Share on other sites

@plicatibu

You don't need to add anything. The above code will scale the game for all devices correctly. But an important thing that has to be taken in consideration is that the game will crop out some areas out of the screen, so you must not position important game realated things on the edges. But align hud elements to the edge of the screen.

Share this post


Link to post
Share on other sites

This is awesome. Thanks!

 

*EDIT: But how do you load a sprite sheet with its correct framewidth and frameheight?

this.game.load.spritesheet('muteBtn','asset/'+BasicGame.screen+"/MuteButton.png",?,?);

Share this post


Link to post
Share on other sites

I had to implement something similar, although due the responsive nature of the game the logical size can change and instead of convert* functions I only have pre-calculated pixel ratio multiplier which is used for all positioning code. Also same mechanism gives the asset prefix for loading correct size assets and is hidden behind Phaser.Loader wrapper. Another solution would have been just change the Loader baseURL, but I needed local atlas objects data also for the textures.

 

Only drawback is the requirement to remember multiply all coordinate stuff (ie. for delta tweens) with the multiplier as this is not that easy to transparently without modifying Phaser.

 

The hi-dpi stuff seems to work fairly well with iOS devices and retina MacBook Pro.

Share this post


Link to post
Share on other sites

Hello and thanks for this post.

 

I'm struggling a bit with orientation in my game so i tried this post. And i keep getting an error in these 3 lines.

 

Error

Uncaught Error: listener is a required param of add() and should be a Function. 
this.scale.hasResized.add(this.gameResized, this);this.scale.enterIncorrectOrientation.add(this.enterIncorrectOrientation, this);this.scale.leaveIncorrectOrientation.add(this.leaveIncorrectOrientation, this);

Can someone tell me why ?

Thanks

Share this post


Link to post
Share on other sites

Hello and thanks for this post.

 

I'm struggling a bit with orientation in my game so i tried this post. And i keep getting an error in these 3 lines.

 

Error

Uncaught Error: listener is a required param of add() and should be a Function. 
this.scale.hasResized.add(this.gameResized, this);this.scale.enterIncorrectOrientation.add(this.enterIncorrectOrientation, this);this.scale.leaveIncorrectOrientation.add(this.leaveIncorrectOrientation, this);

Can someone tell me why ?

Thanks

 

 

You probably forgot to add the functions for handling the orientation switching, that's a normal error if your callbacks are undefined.

 

Try adding these lines:

 

 

this.enterIncorrectOrientation = function(event, message){

    // do stuff here when in incorrect orientation

    console.log(You now have incorrect orientation');

};

 

 

this.leaveIncorrectOrientation = function(event, message){

    // do stuff here when in correct orientation

     console.log('You now have orientation');

};

Share this post


Link to post
Share on other sites

Hello and thank you for the code examples!

 

How can I get ScaleManager2.js to work with typescript? I keep getting The property 'NO_BORDER' does not exist on value of type 'Phaser.ScaleManager'.

 

Thanks again!

 

EDIT: I've just opened a new topic seeking how to achieve this in typescript http://www.html5gamedevs.com/topic/6689-how-to-scale-a-game-for-different-resolutions-typescript-version/

Share this post


Link to post
Share on other sites

Hello and thank you for the code examples!

 

How can I get ScaleManager2.js to work with typescript? I keep getting The property 'NO_BORDER' does not exist on value of type 'Phaser.ScaleManager'.

 

Thanks again!

 

EDIT: I've just opened a new topic seeking how to achieve this in typescript http://www.html5gamedevs.com/topic/6689-how-to-scale-a-game-for-different-resolutions-typescript-version/

 

app.ts

game.ts

boot.ts

preloader.ts

 

I see no reason to post menu and levels.

Share this post


Link to post
Share on other sites

Sorry, I am not so familiar with typescript. Hope someone will post a solution for your problem.

If you are comfortable with typescript, you can try to code the typescript version.

Your solution already coded in my post in typescript.

Share this post


Link to post
Share on other sites

Hi all!

First sorry for my poor English.

I read some articles and tested them , but they work only 1-2-3 device.

I proved to doing with the viewport scaling, but that also don't work, or when i doing the refresh in the page or when simply loaded.

My test are there:http://gaboritaly.altervista.org/ph/fs/2/  and http://gaboritaly.altervista.org/ph/fs/2a/

But what I really want this scaled and centered the stage with the fluid positioned elements (my bg is a little bigger (360 × 570)  than the default size to remedy the different screen sizes but the safe zone is 320x440):

 my.jpg

 

@vinod777 first compliment for your code, I think that is a better solution to preload the right image, and will have the possibility use the fluid positioned elements.

I tested  your code example on different devices, but same case work well only if the page loaded only in landscape mode, or if i did the refresh on the page.

 

different-device.jpg

 

I'm really newbie in phaser, and I would to want test your code, but I don't think to have that code level what you have, anyway what I can, test in some device any solution.

And how I know in phaser are some things what will be careful, camera, world etc…  

Thanks a lot for any idea, and have a nice evening.

Share this post


Link to post
Share on other sites

Hi everyone, first post here, so I'm a bit of a newbie too.

 

I'd like to scale my game using the technique outlined here, but I've created it using the Yeoman generator and main.js is replaced whenever I make a change to any of my state files. Does anyone know if there's a way to get round this?

 

Thanks for any help,

 

Paul

Share this post


Link to post
Share on other sites

Hi everyone, first post here, so I'm a bit of a newbie too.

 

I'd like to scale my game using the technique outlined here, but I've created it using the Yeoman generator and main.js is replaced whenever I make a change to any of my state files. Does anyone know if there's a way to get round this?

 

Thanks for any help,

 

Paul

It's a bit offtopic, but you will have a folder named templates and within you will find _main.js.tpl, from which grunt will build your main.js. Also have a look inside on config.json; the game/canvas size will be stored here.

Share this post


Link to post
Share on other sites

Hi Vinod,

 

thank you for your script and all the explanations.

It is working great on iphone, but on the android devices I have tested the navigation bar stays and hide the upper part of the game ( sansung galaxy s2, s4, and galaxy tab3 llite). 

Is there a way to detect this behavior and adapt the screen size accordingly ?

 

thank you

Share this post


Link to post
Share on other sites

Hi Vinod777 or all other Members,

 

first of all, great scaling solution.

After update phaser from Version 2.1.3 to 2.2.2 there are some problems with the canvas size.

 

Do you know what could be the problem here and how to fix it? On Version 2.1.3  everything is fine, but with a newer phaser version the canvas is not correct scaled. On Iphone 4 is all fine, but on the other, the sprites looks squeezed. They miss pixels.

 

With Version 2.2.0 there are some changes with the scaleManager, may be this could be one of the problems?!

 

Thanks for your help.

 

Regards,

Chris

 

 

Version: 2.1.3

IPhone 4:

<canvas width="640" height="960" style="display: block; width: 320px; height: 480px; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); cursor: inherit;"></canvas>

 

IPhone 5:

<canvas width="640" height="960" style="display: block; width: 378.666666666667px; height: 568px; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); cursor: inherit;"></canvas>

 

IPhone 6:

<canvas width="640" height="960" style="display: block; width: 449.604569420035px; height: 675px; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); cursor: inherit;"></canvas>

 

IPhone 6 Plus:

<canvas width="640" height="960" style="display: block; width: 490.666666666667px; height: 736px; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); cursor: inherit;"></canvas>

 

 

Version: 2.2.2

IPhone 4:

<canvas width="640" height="960" style="display: block; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); width: 320px; height: 480px; cursor: inherit; margin-left: 0px; margin-top: 0px;"></canvas>

 

IPhone 5:

<canvas width="640" height="960" style="display: block; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); width: 320px; height: 568px; cursor: inherit; margin-left: 0px; margin-top: 0px;"></canvas>

 

IPhone 6:

<canvas width="640" height="960" style="display: block; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); width: 375px; height: 667px; cursor: inherit; margin-left: 0px; margin-top: 0px;"></canvas>

 

IPhone 6 Plus:

<canvas width="640" height="960" style="display: block; touch-action: none; -webkit-user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); width: 414px; height: 736px; cursor: inherit; margin-left: 0px; margin-top: 0px;"></canvas>

Share this post


Link to post
Share on other sites

Hi,

 

is there anybody who can help me or give me some hints?

I want to display my game on many devices, especially for iPhone 4, 5 and 6 in full screen.

I don't want letterboxes. Cutting some game-width is ok for me, because important elements are on top and bottom. The width is dynamically with an animated tilesprite.

This means, only the height is important for my game.

 

This scaling solution is the best for my requirement, or do you know other great solutions?

I can not set width=window.innerWidth * devicePixelRatio and the same for height, because i need the same game viewport/area/playground for different devices.

 

Wrong

IPhone4: 960px / 48px = 20 tiles

IPhone5: 1136px / 48px = 23.6 tiles

 

Correct with this great scaling solution!

IPhone4: 960px / 48px = 20 tiles

IPhone5 (Scaled from IPhone4): 960px / 48px = 20 tiles

 

Regards,

Chris

Share this post


Link to post
Share on other sites

Hi chris1986m,

I haven't tried the scaling solution on new phaser versions yet as I was busy on other projects. I believe the scaling is changed on newer phaser versions. If you have some coding experience, you can modify it for the latest phaser. Basically the scaling is like letterbox first, then increase the scale by screenwidth/letterboxwidth.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.