Deux fonctions rotation en Java.
RésoluKX Messages postés 19031 Statut Modérateur -
Voilà, j'ai deux fonctions de rotation en Java et sur les deux ça ne va pas.
La première s'occupe de tous les angles sauf 90 180 et 270.
Et la deuxième ne s'occupe que des angles 90 180 et 270.
Si quelqu'un pouvez m'aider. Merci beaucoup !
public static void rotation(int angle)
{
// Fonction chargée de la rotation d'une image pour tout angle sauf les multiples de 90°.
int l=0,h=0,i=0,j=0,pix=0,phd=0,pbd=0;
// l et h pour la largueur et la hauteur de l'image de départ.
// i et j pour deux boucles pour imbriquées.
// phd signifie pixel en haut à droite, pbd signifie pixel en bas à droite car ils vont servir à trouver la nouvelle taille de l'image.
BufferedImage im=null,imro=null;
// On déclare deux images, celle de départ im et celle d'arrivée imro. Au départ elles sont toutes les deux noires.
try {
im = ImageIO.read(new File("./image.bmp"));
}
catch (Exception e) {}
// On charge l'image qui s'appelle im.
l = im.getWidth();
// Récupération de la largueur de l'image de départ.
h = im.getHeight();
// Récupération de la hauteur de l'image de départ.
phd = (int) ((double)((Math.abs((l-1)*Math.cos(angle*Math.PI/180)))));
// Calcul des coordonnées du point phd.
// Formule (avec angle en radian) : x' = x*cos(angle) - y*sin(angle) or y de phd = 0
pbd = (int) ((double)((Math.abs((l-1)*Math.sin(angle*Math.PI/180) + (h-1)*Math.cos(angle*Math.PI/180)))));
// Calcul des coordonnées du point pbd.
// Formule (avec angle en radian) : y' = x*sin(angle) + y*cos(angle)
imro = new BufferedImage(phd+1,pbd+1,BufferedImage.TYPE_INT_RGB);
// On crée l'image imro qui sera la nouvelle.
for (j=0;j<h;j++) {
for (i=0;i<l;i++) {
// On parcourt l'image d'origine avec les deux boucles imbriquées.
pix = im.getRGB(i,j);
// On récupère le pixel aux coordonnées i,j.
imro.setRGB(Math.abs((int) (i*Math.cos(angle*Math.PI/180)-j*Math.sin(angle*Math.PI/180))),Math.abs((int) (i*Math.sin(angle*Math.PI/180) + j*Math.cos(angle*Math.PI/180))),pix);
// Grâce à la formule précédemment énoncé, on calcule les coordonnées du pixel récupéré et on l'implante dans la nouvelle image.
}
}
try {
ImageIO.write(imro,"png",new File("rotation.png"));
}
catch (Exception e) {};
// On sauvegarde l'image.
}
public static void rotation90(int nb, BufferedImage im)
{
// Petite fonction "récursive" chargée de faire pivoter une image d'un angle uniquement de 90, 180 ou 270.
// Elle prend en paramètres un chiffre soit 1, 2 ou 3 et une image. (1 pour 90°, 2 pour 180° et 3 pour 270°)
int l=0,h=0,i=0,j=0,pix=0;
// l et h largueur et hauteur de l'ancienne image.
BufferedImage imro;
// Déclaration de la nouvelle image.
l = im.getWidth();
// Récupération de la largueur de l'image de départ.
h = im.getHeight();
// Récupération de la hauteur de l'image de départ.
imro = new BufferedImage(h,l,BufferedImage.TYPE_INT_RGB);
// On crée l'image imro qui sera la nouvelle.
// Sachant que l'angle est soit 90, 180 ou 270, il faut au moins la faire tourner une fois.
for (j=0;j<h;j++) {
for (i=0;i<l;i++) {
//On parcout l'image d'origine.
pix = im.getRGB(i,j);
//Récupération du pixel aux coordonnées i,j
imro.setRGB(h-j-1,l-i-1,pix);
//On dépose le pixel d'origine au bon endroit sur la nouvelle image sachant que :
// h-j-1 correspond à j dans le sens inverse (i décroissant).
// l-i-1 correspond à i dans le sens inverse (j décroissant).
}
}
if (nb == 1) {
// Si on la fait tourner une fois alors.
try {
ImageIO.write(imro,"png",new File("rotation.png"));
}
catch (Exception e) {};
// On sauvegarde tout de suite car c'est déjà fait.
// Si jamais c'était deux fois ou trois fois, on remonte dans la récursivité.
}
else {
if(nb == 2) {
// Sinon si on doit la faire pivoter d'un angle de 180°
rotation90(1,imro);
// Alors comme on a déjà fait une fois, on ne va relancer qu'une fois de plus une rotation de 90°
}
else {
// Sinon, c'est nécessairement une rotation de 3 fois 90° donc 270 car dans le programme principal il n'envoit que 1, 2 ou 3 à la fonction.
rotation90(2,imro);
// Donc on doit relancer la fonction encore deux fois.
}
}
}
- Math sin java
- Jeux java itel - Télécharger - Jeux vidéo
- Formule math - Télécharger - Études & Formations
- Jeux java football - Télécharger - Jeux vidéo
- Java apk - Télécharger - Langages
- Jeux java itel 5360 - Forum Mobile
12 réponses
Problème central : deux fonctions de rotation en Java traitent différemment les angles, l'une couvrant tous les angles sauf les multiples de 90°, l'autre ne gère que 90°, 180° et 270°. Des solutions portent sur le recalcul de la taille en fonction des coins après rotation et l'application des formules x' = x cos(angle) - y sin(angle), y' = x sin(angle) + y cos(angle). D'autres propositions suggèrent d'agrandir l'image et d'ajouter xmin, ymin, xmax, ymax pour éviter les trous, tout en envisageant l'utilisation d'alphas et de méthodes comme WritableRaster. En pratique, certains recommandent aussi de tourner par incréments ou de traiter les cas spécifiques via une approche récursive ou par fractions de pixels pour limiter les pertes et les chevauchements.
Il suffit de bien gérer les indices et c'est bon, par contre appeler 2 ou 3 fois une rotation à 90° pour faire 180° et 270° c'est vraiment très moche !
Pour la rotation quelconque, si tu fais un positionnement de points avec des sinus et des cosinus tu risques de te retrouver avec des pixels vides !
Il faudrait peut-être d'abord augmenter la taille de l'image (pour grossir les pixels), et remplir les trous avec les voisins, avant de rétablir la proportion originale de l'image.
Remarque : il est surement possible de faire toutes ces opérations en une seule étape mais il faut bien calculer les valeurs (c'est plus compliqué que juste cos et sin)
Mon int nb me servirait quand même pour définir les indices c'est ça ?
Pour la rotation quelconque, je grossis l'image mais est-ce que ça suffit si je la grossis deux fois ? Ensuite je fais la rotation, et je remplis les trous c'est cela ?
Dernière question, comment fais-t-on s'il vous plaît en Java pour mettre un pixel en transparent.
Merci beaucoup d'avance.
imro.setRGB(h-j-1,l-i-1,pix);Je n'ai pas regardé si elle était bonne, mais l'idée est là en tout cas, et il suffit de faire pareil pour 180° et 270°
Pour la rotation quelconque, l'intérêt d'augmenter la taille est de pouvoir utiliser des "fractions de pixels" pour faire la rotation, en effet tes sinus et cosinus ne seront pas entiers donc avec les arrondis ça peut mettre deux pixels au même endroit ou aucun à un endroit et donc former un "trou".
Plus tu augmentes la taille plus la fraction de pixels sera importante, et la rotation n'en sera que plus esthétiquement appréciable, mais il ne faut pas trop multiplier pour autant car augmenter par N la taille multiplie le nombre de pixels par N² (qui est donc gourmand en mémoire et temps de calcul), d'autant que tout façon le but est ensuite de rediviser ce que tu auras multiplié, et tu vas donc devoir mélanger plusieurs pixels en un seul (en faisant la moyenne, ou alors avec une majorité) ce qui d'ailleurs peut te permettre de combler les trous en les ignorants lors de la division...
Donc le choix de la multiplication est un dilemme, à mon avis il faut tester pour quelques valeurs et voir ce qui est le mieux... mais l'idéal est de rajouter un paramètre à la méthode pour laisser l'utilisateur choisir.
Pour mettre un pixel en transparent tu doit utiliser la composante alpha des Color, ce qui doit se faire par exemple en manipulant le WritableRaster que tu obtiens avec getAlphaRaster()
Exemple:
Sans multiplication ta rotation (cosinus et sinus) te dit de placer un pixel en x=12.53 y=17.35
Or x et y sont des entiers tu vas donc les placer en 12 et 17, mais la conversion en entier va t'induire un décalage et faire que ta rotation sera moche.
En multipliant par deux (avec les même valeurs) tu devras placer ton pixel en x=25.06 y=34.70
En passant à l'entier ça va te donner 25 et 34, c'est donc plus précis car en anticipant sur le division derrière, c'est comme si tu avais placé ton x en 12.5 ce qui n'était pas possible en conservant la taille de départ. En multipliant par 3, ça va te donner x=37 et y=52 correspondant à 12.33 et 17.33 etc...
La multiplication permet donc de compenser les erreurs de calculs induits par le passage des valeurs réelles à entières du résultats des fonctions trigonométriques et qui vont t'amener à placer plusieurs fois des pixels d'origines différentes sur le même pixel d'arrivée. Augmenter le nombre de pixels va diminuer le nombre de conflits (pour l'image redivisée derrière), ta rotation sera plus fluide.
- J'agrandis l'image par n fois.
- Je crée une nouvelle image de la même taille que mon image agrandie.
- J'utilise le cosinus et le sinus en multipliant les coordonnées x et y par n pour remplir la nouvelle image.
- Cette image qui a subit une rotation, je crée une nouvelle image qui sera de taille l'image qui a été pivoté divisé par n pour réduire l'image de la rotation agrandie.
- Je parcours l'image et je fais la moyenne des n pixels et je place cette moyenne sur la dernière image.
C'est ça ?
Mais comment connaître la dimension de l'image retournée ?
À quoi ça sert de faire la moyenne sachant que par exemple, si l'image est multipliée par deux il y aura deux fois le premier pixel au début ?
Non, il faut que la nouvelle image soit de la taille de l'image agrandie et pivoté.
Par exemple si tu as un carré de taille 10x10, pivoté de 45° ça devient une image 14x14.
Le calcul se fait avec les cosinus et les sinus également, il suffit de connaitre la position des coins de l'image après le pivotement pour définir les bords de la nouvelle image?
"À quoi ça sert de faire la moyenne sachant que par exemple, si l'image est multipliée par deux il y aura deux fois le premier pixel au début"
En fait si tu multiplie par 2, tu auras 4 fois le même pixel, mais une fois pivoté ils ne seront pas nécessairement placé au même endroit car avec les arrondis ils pourraient se chevaucher. D'où l'intérêt d'avoir augmenté l'image, parce que du coup si on en a 4, on a plus de chance d'en garder 1 ou 2. Et du coup ce n'est pas forcément la moyenne qu'il faut faire mais peut-être plutôt gardé le pixel le plus nombreux dans la zone à diminuer.
"Je parcours l'image et je fais la moyenne des n pixels et je place cette moyenne sur la dernière image."
Cette étape ne sert à rien, ou alors, elle doit être incluse dans l'étape "réduire l'image de la rotation"
Et tu as oublié une étape : il faut remplir les trous obtenus à cause des erreurs d'arrondis après avoir fait les cosinus et sinus, et avant de diminuer l'image.
Lors de l'agrandissement, je voulais savoir comment procéder. On copie le même pixel autant de fois que le nombre de fois qu'on a agrandi l'image ??
Exemple : si on a multiplié l'image par deux, il y aura quatre fois chaque même pixel ?
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre questionCar cela m'embête d'avoir deux fonctions agrandissements...
Si tu fais une interpolation linéaire ça n'aura plus de sens parce que les pixels que tu vas faire pivoter ne seront plus ceux de l'image d'origine mais un mélange de plusieurs voisins de l'image agrandie alors qu'en pivotant ces voisins ne seront surement plus les même...
Ici l'interpolation linéaire va te donner de mauvais résultats, en plus c'est beaucoup de calculs pour pas grand chose car copier-coller des pixels en carrés c'est beaucoup plus simple que faire une interpolation.
Vous pouvez regarder s'il vous plaît ?
public static void agrandissementpix(int nb)
{
int i=0,j=0,k=0,m=0,l=0,h=0,pix=0;
BufferedImage im=null,imagpix=null;
try {
im=ImageIO.read(new File("./image.bmp"));
}
catch(Exception e){}
if(nb <= 1) {
System.out.println("L'agrandissement doit etre au moins de 2");
}
else {
l=im.getWidth();
h=im.getHeight();
imagpix= new BufferedImage(l*nb,h*nb,BufferedImage.TYPE_INT_RGB);
for(j=0;j<h;j++){
for(i=0;i<l;i++){
pix=im.getRGB(i,j);
imagpix.setRGB(i*nb,j*nb,pix);
for(k=0;k<=nb;k++) {
for(m=0;m<=nb;m++) {
imagpix.setRGB(i+m,j+k,pix);
}
}
}
}
try {
ImageIO.write(imagpix,"png",new File("redimensionnement.png"));
}
catch (Exception e){};
}
}
De plus si tu fais des try catch avec rien dedans il vaut mieux être sûr de ton coup, or ici sur des IOException, il serait judicieux de mettre au minimum un println(e), sauf que de toute façon, s'il y a une erreur tu ne peux plus continuer donc autant ne pas récupérer l'exception !
Ton test sur la valeur de nb, il vaut mieux le faire avant de lire le fichier, car sinon tu l'auras lu pour rien...
De plus évite d'utiliser des lettres visuellement ambigües par exemple l (L minuscule) et I (i majuscule) .
Et parlons un peu convention, les boucles sont généralement imbriqués par ordre alphabétique (on met d'abord le for sur i, avant le for sur j, d'autant plus ici, qu'en maths i représente les lignes et j les colonnes, et que tu les as inversé. L'idéal ici étant d'utiliser x et y !
Concernant l'erreur en elle même, tu as fait varier k et m jusqu'à nb inclus alors que commençant à 0, ça en fait un de trop à chaque fois ! Sans oublier qu'après tu fais i+m et j+k, ce qui est évidemment faux vu que tu es censé raisonner en multiples de nb.
PS. J'imagine que k et m sont les deux premières lettres que tu as trouvé sur le clavier, mais l'idéal c'est de faire un nommage intelligent qui a un sens dans le contexte !
Une dernière remarque : vu toutes les manipulations d'image que tu souhaites faire, il serait nettement moins coûteux de faire des méthodes qui prennent en entrée un BufferedImage, et renvoie le BufferedImage résultat. Parce que faire de la lecture et écriture de fichiers images c'est très coûteux !
public static void agrandissementpix(String src, String dst, String formatName, int nb) throws IOException
{
if (nb<=1)
{
System.err.println("L'agrandissement doit etre au moins de 2");
return;
}
BufferedImage imgSrc=ImageIO.read(new File(src));
int w=imgSrc.getWidth();
int h=imgSrc.getHeight();
BufferedImage imgDst= new BufferedImage(w*nb,h*nb,BufferedImage.TYPE_INT_RGB);
for(int x=0;x<w;x++)
for(int y=0;y<h;y++)
{
int rgb=imgSrc.getRGB(x,y);
for(int nx=0; nx<nb; nx++)
for(int ny=0; ny<nb; ny++)
imgDst.setRGB(x*nb+nx,y*nb+ny,rgb);
}
ImageIO.write(imgDst,formatName,new File(dst));
}
public static void main(String...args) throws IOException
{
agrandissementpix("./image.bmp","redimenssionement.png","png",2);
}
Alors j'essaie de la refaire mais à cause de l'exception, je n'arrive pas à renvoyer du vent.
"x' = x cos(a) + y sin(a)
y' = x sin(a) - y cos(a)
si a = -alpha
tu as l'équation partant d'un pixel final vers un pixel original..
Tu parcoures simplement l'image finale et applique l'équation à chaque pixel pour avoir le pixel initial (et donc la valeur) à appliquer.."
Actuellement voilà le résultat d'une rotation de 10°
https://imageshack.com/
Je voulais savoir pourquoi le coin en bas à gauche pose problème...
Pour le coin en bas à gauche, c'est ce que je disais, il faut recentrer les résultats car avec les cosinus et les sinus tu obtiens des valeurs négatives !
Remarque :
- Sur ton image on voit bien les pertes de pixels dont je parlais hier (les points noirs un peu partout)
- Je n'ai pas l'impression que tu gères la transparence ici, car tu as du noir par défaut, il faudrait que tu utilises BufferedImage.TYPE_INT_ARGB au lieu de TYPE_INT_RGB
Modification: j'ai réussi à faire la transparence, merci beaucoup. Reste donc le soucis d'agrandir puis travailler sur l'image et enfin le soucis d'en bas à gauche...
Comment je recentre mon résultat ?
En plus, comment je calcule la distance de ma nouvelle image ?
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Pour recentrer le résultat et calculer la taille de ta nouvelle image en fait ça se fait en même temps, il suffit de calculer les positions des 4 coins de l'image tourné, pour avoir Xmin, Xmax, Ymin, Ymax.
La taille de ton image est donc Xmax-Xmin+1, Ymax-Ymin+1, et pour recentrer il suffit de soustraire Xmin, et Ymin (qui sont négatifs) à chaque position x,y et le tour est joué !
Alors, je voudrais récapituler un peu un truc si possible.
Dès que j'ai l'image d'origine, je crée trois nouvelles images.
Une qui sera l'image d'origine agrandie nx sans rotation.
Une qui sera l'image agrandie nx avec rotation.
Et une dernière qui sera l'image finale pivoté.
C'est cela ?
Le problème qui se pose c'est les pixels perdus un peu partout. Il faut trouver une méthode pour combler les trous. Moi je proposais d'augmenter la taille puis de la rediminuer de sorte que les trous soient absorbés lors de la diminution, mais tu peux faire une autre méthode si tu veux, comme par exemple colorer chaque trou en prenant la moyenne des pixels voisins comme couleur.
Mais tu peux commencer par la méthode des moyennes, si le résultat te suffit visuellement tant mieux, sinon tu auras juste à augmenter avant et diminuer après pour améliorer le traitement.
Question agrandissement, on l'a déjà fait hier ici
L'agrandissement pas de soucis, mais eu... Une fois agrandie je ne vois pas exactement comment faire en faire.
Vous pouvez me faire des dessins s'il vous plaît que je vois les étapes ?
En parlant de ça, j'ai arrangé ma fonction pour ajouter ymin xmin ymax et xmax et tout mais ça ne fonctionne pas.
public static void rotation(int angle)
{
// Fonction chargée de la rotation d'une image pour tout angle sauf les multiples de 90°.
int l=0,h=0,i=0,j=0,pix=0,xmax=0,ymax=0,xmin=0,ymin=0;
// l et h pour la largueur et la hauteur de l'image de départ.
// i et j pour deux boucles pour imbriquées.
// phd signifie pixel en haut à droite, pbd signifie pixel en bas à droite car ils vont servir à trouver la nouvelle taille de l'image.
BufferedImage im=null,imro=null;
// On déclare deux images, celle de départ im et celle d'arrivée imro. Au départ elles sont toutes les deux noires.
try {
im = ImageIO.read(new File("./image.bmp"));
}
catch (Exception e) {}
// On charge l'image qui s'appelle im.
l = im.getWidth();
// Récupération de la largueur de l'image de départ.
h = im.getHeight();
// Récupération de la hauteur de l'image de départ.
xmax = (int) Math.abs((l-1)*Math.cos(angle*Math.PI/180));
xmin = (int) Math.abs(-(h-1)*Math.sin(angle*Math.PI/180));
// Calcul des coordonnées des points xmax et xmin qui sont les coordonnées des coins en haut à droite et en bas à gauche.
// Formule (avec angle en radian) : x' = x*cos(angle) - y*sin(angle) or y de phd = 0
ymax = (int) ((Math.abs((l-1)*Math.sin(angle*Math.PI/180) + (h-1)*Math.cos(angle*Math.PI/180))));
// Calcul des coordonnées du point ymax.
// Formule (avec angle en radian) : y' = x*sin(angle) + y*cos(angle)
imro = new BufferedImage(xmax-xmin+1,ymax-ymin+1,BufferedImage.TYPE_INT_ARGB);
// On crée l'image imro qui sera la nouvelle.
for (j=0;j<h;j++) {
for (i=0;i<l;i++) {
// On parcourt l'image d'origine avec les deux boucles imbriquées.
pix = im.getRGB(i,j);
// On récupère le pixel aux coordonnées i,j.
imro.setRGB(Math.abs((int) (i*Math.cos(angle*Math.PI/180)-j*Math.sin(angle*Math.PI/180)))-xmin,Math.abs((int) (i*Math.sin(angle*Math.PI/180) + j*Math.cos(angle*Math.PI/180)))-ymin,pix);
// Grâce à la formule précédemment énoncé, on calcule les coordonnées du pixel récupéré et on l'implante dans la nouvelle image.
}
}
try {
ImageIO.write(imro,"png",new File("rotation.png"));
}
catch (Exception e) {};
// On sauvegarde l'image.
}
Sinon, je ne vois pas pourquoi tu t'embêtes à recalculer à chaque fois tout tes cosinus et sinus en faisant ta conversion degrés/radians, tu n'as qu'à le faire une bonne fois pour toute au début du programme :
double cos = Math.cos(Math.PI*angle/180); double sin = Math.sin(Math.PI*angle/180);
Après, outre le fait que tu refais un code tout moche et où je te renvoie à mes remarques d'hier, tu calcules (xmin,ymin) en considérant que ça correspond nécessairement aux coordonnées du coin supérieur droit une fois tourné, or ce n'est pas toujours le cas, d'ailleurs en général, les valeurs de xmin et de ymin ne correspondront pas forcément au même coin, de même pour (xmax,ymax)
Dessine les rotations à 0°, 45°, 90°, 135° etc... pour t'en convaincre !
abcd[0] = 0;
abcd[1] = (int) (l-1)*cos;
abcd[2] = (int) -(h-1)*sin;
abcd[3] = (int) (l-1)*cos - (h-1)*sin;
abcd[4] = 0;
abcd[5] = (int) (l-1)*sin;
abcd[6] = (int) (h-1)*cos;
abcd[7] = (int) (l-1)*sin + (h-1)*cos;
Sachant que l'ordre dans le tableau est
xA xB xC xD yA yB yC yD xmin xmax ymin ymax
Mais je ne vois toujours pas l'intérêt du tableau ! Car il suffit maintenant de faire Math.min ou Math.max sur les valeurs pour trouver xmin et les autres, ton tableau est vraiment inutile !
Bref, utiliser un tableau uniquement parce que ça fait beaucoup de variables est absurde ici, il est beaucoup plus logique de mettre la valeur xA dans une variable xA que dans la case abcd[0] !
Et évidement que tu dois utiliser Math.min pour calculer xmin, à moins que tu ne veuilles vraiment continuer dans l'absurdité d'un tableau et faire une boucle for pour 4 valeurs...
Les switch c'est la base (et pas que en Java ! Regarde ici), sans parler de toutes les erreurs que j'ai pu relever au cours des différentes discussions que l'on a eu sur ce projet, il y a vraiment des trucs que tu dois revoir dans ta manière de programmer. Le mieux serait de comparer mes remarques avec celles que pourrait avoir ton prof sur ton projet (par exemple sur cette histoire de tableau à la place des variables) parce que si tu as rendu ton code en conservant ces erreurs, il est évident qu'il va te sanctionner, mais ce serait l'occasion qu'il te réexplique son cours sur les points que j'ai pu critiquer.