Merci pour les réponses Eroy, G. et pour les détails Laurent !
Voici donc les sources de cette classe NinePatch que je viens de créer pou gérer ces images, que je dois donc maintenant optimiser un peu ( notamment pour gérer les zoom <1 ) :
NinePatch.h
/*
* NinePatch.h
*
* Created on: 8 mai 2013
* Author: Dragonic
*/
#ifndef NINEPATCH_H_
#define NINEPATCH_H_
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
class NinePatch
{
public:
NinePatch(std::string url);
void draw(sf::Vector2f pos, sf::Vector2f size, sf::RenderWindow &window);
sf::Texture getTextureResize(sf::Vector2f size);
sf::Texture getSourceTexture()
{
sf::IntRect rect=sf::IntRect(1, 1, m_width, m_height);
sf::Sprite sprite=sf::Sprite(m_texture, rect);
return *sprite.getTexture();
}
sf::Texture getSourceTextureFull()
{
return m_texture;
}
protected:
private:
sf::Sprite m_subImages[9];
sf::Texture m_texture;
sf::IntRect m_rect[9];
unsigned int m_width;
unsigned int m_height;
};
#endif /* NINEPATCH_H_ */
NinePatch.cpp
/*
* NinePatch.cpp
*
* Created on: 9 mai 2013
* Author: Dragonic
*/
#include "NinePatch.h"
NinePatch::NinePatch(std::string url)
{
sf::Image image;
if (!image.loadFromFile(url))
{
std::cout<<"error chargement image NinePatch"<<std::endl;
}
m_texture.loadFromImage(image);
unsigned int w=image.getSize().x;
unsigned int h=image.getSize().y;
m_width = w-2;
m_height = h-2;
unsigned int x1=0;
unsigned int y1=0;
unsigned int x2=w-1;
unsigned int y2=h-1;
while((image.getPixel(x1, 0)) != sf::Color::Black)//on cherche le 1er pixel non transparent
{
x1++;
}
if(x1!=x2)
{
while((image.getPixel(x2, 0)) != sf::Color::Black)
{
x2--;
}
}
while((image.getPixel(0, y1)) != sf::Color::Black)
{
y1++;
}
if(y1!=y2)
{
while((image.getPixel(0, y2)) != sf::Color::Black)
{
y2--;
}
}
if(x1==x2 && y1==y2)//aucune ligne trouvé
{
std::cout<<"error NinePatch non configuré"<<std::endl;
}
m_rect[0]=sf::IntRect(1, 1, x1, y1);
m_subImages[0]=sf::Sprite(m_texture, m_rect[0]);
m_rect[1]=sf::IntRect(1+x1, 1, x2-x1-1, y1);
m_subImages[1]=sf::Sprite(m_texture, m_rect[1]);
m_rect[2]=sf::IntRect(1+x2, 1, w-x2-2, y1);
m_subImages[2]=sf::Sprite(m_texture, m_rect[2]);
m_rect[3]=sf::IntRect(1, 1+y1, x1, y2-y1);
m_subImages[3]=sf::Sprite(m_texture, m_rect[3]);
m_rect[4]=sf::IntRect(1+x1, 1+y1, x2-x1-1, y2-y1);
m_subImages[4]=sf::Sprite(m_texture, m_rect[4]);
m_rect[5]=sf::IntRect(1+x2, 1+y1, w-x2-2, y2-y1);
m_subImages[5]=sf::Sprite(m_texture, m_rect[5]);
m_rect[6]=sf::IntRect(1, 1+y2, x1, h-y2-2);
m_subImages[6]=sf::Sprite(m_texture, m_rect[6]);
m_rect[7]=sf::IntRect(1+x1, 1+y2, x2-x1-1, h-y2-2);
m_subImages[7]=sf::Sprite(m_texture, m_rect[7]);
m_rect[8]=sf::IntRect(1+x2, 1+y2, w-x2-2, h-y2-2);
m_subImages[8]=sf::Sprite(m_texture, m_rect[8]);
};
void NinePatch::draw(sf::Vector2f pos, sf::Vector2f size, sf::RenderWindow &window)
{
float wScale = (size.x - m_rect[0].width - m_rect[2].width) / m_rect[1].width;
float hScale = (size.y - m_rect[0].height - m_rect[6].height) / m_rect[3].height;
sf::Sprite subImages[9];
for(int i=0;i<9;i++)
{
subImages[i]=m_subImages[i];
}
subImages[0].setPosition(pos);
window.draw(subImages[0]);
subImages[1].setPosition(pos.x+m_rect[0].width, pos.y);
subImages[1].setScale(wScale,1);
window.draw(subImages[1]);
subImages[2].setPosition(pos.x+m_rect[0].width+m_rect[1].width*wScale, pos.y);
window.draw(subImages[2]);
subImages[3].setPosition(pos.x, pos.y+m_rect[0].height);
subImages[3].setScale(1,hScale);
window.draw(subImages[3]);
subImages[4].setPosition(pos.x+m_rect[3].width, pos.y+m_rect[1].height);
subImages[4].setScale(wScale,hScale);
window.draw(subImages[4]);
subImages[5].setPosition(pos.x+m_rect[3].width+m_rect[4].width*wScale, pos.y+m_rect[2].height);
subImages[5].setScale(1,hScale);
window.draw(subImages[5]);
subImages[6].setPosition(pos.x, pos.y+m_rect[0].height+m_rect[3].height*hScale);
window.draw(subImages[6]);
subImages[7].setPosition(pos.x+m_rect[6].width, pos.y+m_rect[1].height+m_rect[4].height*hScale);
subImages[7].setScale(wScale,1);
window.draw(subImages[7]);
subImages[8].setPosition(pos.x+m_rect[6].width+m_rect[7].width*wScale, pos.y+m_rect[2].height+m_rect[5].height*hScale);
window.draw(subImages[8]);
}
sf::Texture NinePatch::getTextureResize(sf::Vector2f size)
{
float wScale = (size.x - m_rect[0].width - m_rect[2].width) / m_rect[1].width;
float hScale = (size.y - m_rect[0].height - m_rect[6].height) / m_rect[3].height;
sf::Sprite subImages[9];
for(int i=0;i<9;i++)
{
subImages[i]=m_subImages[i];
}
sf::RenderTexture renderTexture;
renderTexture.create(size.x,size.y);
renderTexture.clear(sf::Color::Transparent);
subImages[0].setPosition(0,0);
renderTexture.draw(subImages[0]);
subImages[1].setPosition(0+m_rect[0].width, 0);
subImages[1].setScale(wScale,1);
renderTexture.draw(subImages[1]);
subImages[2].setPosition(0+m_rect[0].width+m_rect[1].width*wScale, 0);
renderTexture.draw(subImages[2]);
subImages[3].setPosition(0, 0+m_rect[0].height);
subImages[3].setScale(1,hScale);
renderTexture.draw(subImages[3]);
subImages[4].setPosition(0+m_rect[3].width, 0+m_rect[1].height);
subImages[4].setScale(wScale,hScale);
renderTexture.draw(subImages[4]);
subImages[5].setPosition(0+m_rect[3].width+m_rect[4].width*wScale, 0+m_rect[2].height);
subImages[5].setScale(1,hScale);
renderTexture.draw(subImages[5]);
subImages[6].setPosition(0, 0+m_rect[0].height+m_rect[3].height*hScale);
renderTexture.draw(subImages[6]);
subImages[7].setPosition(0+m_rect[6].width, 0+m_rect[1].height+m_rect[4].height*hScale);
subImages[7].setScale(wScale,1);
renderTexture.draw(subImages[7]);
subImages[8].setPosition(0+m_rect[6].width+m_rect[7].width*wScale, 0+m_rect[2].height+m_rect[5].height*hScale);
renderTexture.draw(subImages[8]);
renderTexture.display();
return renderTexture.getTexture();
}
Et un exemple d'utilisation :
window.clear(sf::Color::White);
NinePatch ninePatch("resources/btn.9.png");
ninePatch.draw(sf::Vector2f(200,200),sf::Vector2f(200,100),window);
sf::Sprite sprite2;
sf::Texture texture2;
texture2 = ninePatch.getTextureResize(sf::Vector2f(100,300));
sprite2.setTexture(texture2);
sprite2.setPosition(50,10);
window.draw(sprite2);
sf::Sprite sprite3;
sf::Texture texture3;
texture3 = ninePatch.getSourceTexture();
sprite3.setTexture(texture3);
sprite3.setPosition(200,10);
window.draw(sprite3);
sf::Sprite sprite4;
sf::Texture texture4;
texture4 = ninePatch.getSourceTextureFull();
sprite4.setTexture(texture4);
sprite4.setPosition(350,10);
window.draw(sprite4);
ce code fourni le résultat suivant :
(http://image.noelshack.com/fichiers/2013/19/1368092765-sans-titre.jpg)
Et si vous voulez l'image source btn.9.png :
(http://image.noelshack.com/fichiers/2013/19/1368092868-btn-9.png)
Alors ce qu'il faut savoir, c'est qu'une image de type NinePatch, c'est une image avec une ligne de pixel en plus de chaque côté pour définir les règles de redimensionnement et de contenu. En haut et à gauche les traits noirs définissent les zones ayant le droit d'être redimensionnées.
Je n'ai pas encore rajouter la fonctionnalité pour les lignes à mettre à droite et en bas, qui définissent la zone de contenu, bien pratique pour ensuite placer des éléments (texte ou autre) dans la texture redimensionnée. Néanmoins donc il faut bien rajouter une ligne de pixel à droite et en bas quand même par défaut pour que le NinePatch soit correct.
Enfin bref, pour l'instant les résultats produits me suffisent ^^ !
sf::Texture getSourceTexture()
{
sf::IntRect rect=sf::IntRect(1, 1, m_width, m_height);
sf::Sprite sprite=sf::Sprite(m_texture, rect);
return *sprite.getTexture();
}
Cette fonction ne sert strictement à rien, quoique tu fasses à ton sprite, sa texture ne sera jamais modifiée.
Sinon, pourquoi dupliquer tout le code de draw dans getTextureResize ? Si draw prenait un sf::RenderTarget& plutôt qu'un sf::RenderWindow&, tu pourrais appeler draw dans getTextureResize en passant ta RenderTexture. Et du coup cette dernière fonction ne ferait plus que 3 lignes de code.
Pourquoi faire une copie de tes 9 sprites à chaque fois ? Ca me paraît inutile par rapport à ce que tu en fais ensuite.
Dans ton exemple : c'est vraiment pas terrible de charger le truc en plein milieu du dessin (ça laisse supposer que tu le fais à chaque fois).
Ensuite, des considérations un peu plus conceptuelles. Déjà, getTextureResize ne me paraît pas utile. Ca sert à quoi de recréer de toute pièce une nouvelle texture avec l'image finale du truc étiré, alors que tu as déjà une fonction qui peut dessiner un même NinePatch à n'importe quelle taille sans que ce soit trop coûteux ?
Bon, on peut se dire que tu veuilles économiser un maximum, et pouvoir dessiner un NinePatch en 1 appel à draw (interne) plutôt que 9. C'est pas idiot. Mais dans ce cas, utilise un vertex array plutôt que 9 sprites : tu pourras le dessiner en un seul appel, tout en gardant la flexibilité de pouvoir choisir la taille dynamiquement.
Ensuite... moi je ferais ça pour vraiment optimiser l'utilisation des ressources :
class NinePatchTexture
{
public:
bool loadFromFile(const std::string&);
private:
friend class NinePatchSprite;
enum Area {TopLeft, Top, TopRight, ... };
sf::Texture& getTexture();
sf::IntRect getRect(Area area);
};
class NinePatchSprite : public sf::Transformable, public sf::Drawable // cf. les tutos
{
public:
void setTexture(const NinePatchTexture& texture);
void setSize(sf::Vector2f size);
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const;
NinePatchTexture* m_texture;
sf::Vertex vertices[36];
}
NinePatchTexture texture;
texture.loadFromFile("...");
NinePatchSprite button1;
button1.setTexture(texture);
button1.setSize(sf::Vector2f(100, 50));
NinePatchSprite button2;
button2.setTexture(texture);
button2.setSize(sf::Vector2f(200, 100));
...
window.draw(button1);
window.draw(button2);
Je reviens sur un petit problème que je ne comprends pas bien !
J'ai remarqué après avoir passé mon programme à quelques amis pour tester que certains voyaient le programme planté à un moment précis.
Ayant recherché la ligne de code où ça plantait c'était incompréhensible, car c'était un glDrawElement qui faisait crasher l'appli (celui de ma skybox), et qui n'avait strictement rien à voir au niveau de la manip pour faire crasher. En gros c'est comme si quelquechose avait massacré ma VRAM d'un coup.
Après quelques recherches j'ai remarqué que le souci venait finalement de ma nouvelle classe NinePatch, et plus particulièrement de sa fonction getTextureResize() qui est sensé écrire les données dans un RenderTexture et me renvoyer la texture correspondante à la fin des draw.
NinePatch.cpp
sf::Texture NinePatch::getTextureResize(sf::Vector2f size)
{
float wScale = (size.x - m_rect[0].width - m_rect[2].width) / m_rect[1].width;
float hScale = (size.y - m_rect[0].height - m_rect[6].height) / m_rect[3].height;
sf::Sprite subImages[9];
for(int i=0;i<9;i++)
{
subImages[i]=m_subImages[i];
}
sf::RenderTexture renderTexture;
renderTexture.create(size.x,size.y);
renderTexture.clear(sf::Color::Transparent);
subImages[0].setPosition(0,0);
renderTexture.draw(subImages[0]);
subImages[1].setPosition(0+m_rect[0].width, 0);
subImages[1].setScale(wScale,1);
renderTexture.draw(subImages[1]);
subImages[2].setPosition(0+m_rect[0].width+m_rect[1].width*wScale, 0);
renderTexture.draw(subImages[2]);
subImages[3].setPosition(0, 0+m_rect[0].height);
subImages[3].setScale(1,hScale);
renderTexture.draw(subImages[3]);
subImages[4].setPosition(0+m_rect[3].width, 0+m_rect[1].height);
subImages[4].setScale(wScale,hScale);
renderTexture.draw(subImages[4]);
subImages[5].setPosition(0+m_rect[3].width+m_rect[4].width*wScale, 0+m_rect[2].height);
subImages[5].setScale(1,hScale);
renderTexture.draw(subImages[5]);
subImages[6].setPosition(0, 0+m_rect[0].height+m_rect[3].height*hScale);
renderTexture.draw(subImages[6]);
subImages[7].setPosition(0+m_rect[6].width, 0+m_rect[1].height+m_rect[4].height*hScale);
subImages[7].setScale(wScale,1);
renderTexture.draw(subImages[7]);
subImages[8].setPosition(0+m_rect[6].width+m_rect[7].width*wScale, 0+m_rect[2].height+m_rect[5].height*hScale);
renderTexture.draw(subImages[8]);
renderTexture.display();
return renderTexture.getTexture();
}
Le programme ne plante pas si je commente "renderTexture.create(size.x,size.y);".
Bien sûr en résultat dans ce cas je n'ai rien comme texture finale, mais c'est pour l'instant la meilleure piste que j'ai pour régler ce souci !
Pour information, le code a été testé sur plusieurs PCs, et aux retours de configs de chacun d'entre-eux, la seule information commune aux PCs qui crachent est qu'ils sont équipés de cartes nVidia !
Voilà, je n'arrive pas à comprendre plus et j'aurais besoin d'aide désormais pour résoudre ce petit souci !
Merci d'avance !
J'ai réussi à créer un code minimal reproduisant le problème :
#include <GL/glew.h>
#include <SFML/Window.hpp>
#include <SFML/OpenGL.hpp>
#include <SFML/Graphics.hpp>
#include <iostream>
using namespace std;
using namespace sf;
void createSquare(GLuint &vaoID, GLuint &vboID, GLuint &iboID) {
float* vertices = new float[12]; // Sommets du carré
vertices[0] = -0.5; vertices[1] = -0.5; vertices[2] = 0.0; // Coin en bas à gauche
vertices[3] = -0.5; vertices[4] = 0.5; vertices[5] = 0.0; // Coin en haut à gauche
vertices[6] = 0.5; vertices[7] = 0.5; vertices[8] = 0.0; // Coin en haut à droite
vertices[9] = 0.5; vertices[10] = -0.5; vertices[11] = 0.0; // Coin en bas à droite
GLubyte Indices[] = {
// Top
1, 2, 0,
3, 0, 2
};
glGenVertexArrays(1, &vaoID); // Créer le VAO
glBindVertexArray(vaoID); // Lier le VAO pour l'utiliser
glGenBuffers(1, &vboID); // Générer le VBO
glBindBuffer(GL_ARRAY_BUFFER, vboID); // Lier le VBO
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(GLfloat), vertices, GL_STATIC_DRAW); // Définir la taille, les données et le type du VBO
glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0); // Définir le pointeur d'attributs des sommets
glGenBuffers(1, &iboID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); // Désactiver le VAO
glBindVertexArray(0); // Désactiver le VBO
delete [] vertices; // Supprimer les sommets
}
Texture getTextureFromRenderTexture()
{
RenderTexture renderTexture;
if(!(renderTexture.create(50,50)))
std::cout<<"failed create renderTexture"<<std::endl;
return renderTexture.getTexture();
}
int main()
{
RenderWindow window;
window.create(VideoMode(800, 600), "test bug RenderTexture", Style::Default, ContextSettings(32));
window.setVerticalSyncEnabled(true);
GLenum initialisationGLEW( glewInit() );
if(initialisationGLEW != GLEW_OK)
{
cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << endl;
}
Event event;
bool running=true;
unsigned int vaoID; // VAO
unsigned int vboID; // VBO
unsigned int iboID; // IBO
createSquare(vaoID, vboID, iboID);
Texture texture;
while (running)
{
window.setActive();
while (window.pollEvent(event))
{
if (event.type == Event::Closed)
{
// on stoppe le programme
running = false;
}
else if ((event.type == Event::KeyPressed) && (event.key.code == Keyboard::T) )
{
texture = getTextureFromRenderTexture();
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(vaoID); // Lier le VAO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
glBindVertexArray(0); // Délier le VAO
window.display();
}
window.close();
return 0;
}
Le programme plante sur mon PC nVidia si j'appuie sur T, au niveau du glDrawElements !
J'ai essayé avec ceci à la place de l'utilisation du VAO :
glBegin(GL_LINES);
glVertex2i(0,0);glVertex2i(0,1);
glVertex2i(0,0);glVertex2i(1,0);
glVertex2i(0,0);glVertex3i(0,0,1);
glEnd();
Et cela ne plante pas lors de l'appuie sur T.
De même, testé à l'instant avec seulement "glDrawArrays(GL_TRIANGLES, 0, 3);" (donc sans me servir de l'IBO), et aucun problème. Il semblerait donc que le bug se limite à l'utilisation de glDrawElements.
Et enfin, autre détail, si "texture = getTextureFromRenderTexture();" est placé avant la boucle principale il n'y a aucun problème. Mais dès qu'il est dedans (par forcément au sein donc d'une réponse à un Event), le programme plante.
A noter donc que si je commente le "renderTexture.create(50,50)" il n'y a aucun problème ! La cause du problème semble donc venir de là !
Voilà, je peux difficilement faire plus précis ^^ !