La toile HTML5 se traduit après l'échelle et la rotation

J'essaie de faire quelque chose avec une toile. D'abord, un utilisateur télécharge une image, si l'image est plus grande que je le souhaite, j'ai besoin de la réduire. Cette partie fonctionne très bien. Récemment, nous avons rencontré un problème avec les utilisateurs d'iPhone qui téléchargent des images. Ils ont des problèmes d'orientation. J'ai compris comment extraire l'orientation, mon problème est ce qui se passe lorsque je manipule l'image dans le canevas.

C'est ce que je dois faire: Obtenez l'image, traduisez (), échelle (), faites pivoter (), traduisez () <- remettez-la à sa position d'origine, drawImage ().

Quand je fais cette partie de l'image est éteinte dans l'abîme.

if (dimensions[0] > 480 || dimensions[1] > 853) { // Scale the image. var horizontal = width > height; if (horizontal) { scaledHeight = 480; scaleRatio = scaledHeight / height; scaledWidth = width * scaleRatio; } else { scaledWidth = 640; scaleRatio = scaledWidth / width; scaledHeight = height * scaleRatio; } canvas['width'] = scaledWidth; canvas['height'] = scaledHeight; ctx['drawImage'](image, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight); /* Rotate Image */ orientation = 8; //manual orientation -> on the site we use loadImage to get the orientation if(orientation != 1){ switch(orientation){ case 8: case 6: canvas.width = scaledHeight; canvas.height = scaledWidth; break; } var halfScaledWidth = scaledWidth/2; var halfScaledheight = scaledHeight/2; ctx.save(); //<- SAVE ctx.clearRect(0,0,canvas.width,canvas.height); ctx.translate(halfScaledWidth,halfScaledheight); switch(orientation){ case 8: //rotate left ctx.scale(scaleRatio,scaleRatio); ctx.rotate(-90*Math.PI/180); ctx.translate(-1380,-1055); // <-Manuial numbers break; case 3: //Flip upside down ctx.scale(scaleRatio,scaleRatio); ctx.rotate(180*Math.PI/180); ctx.translate(-925,-595); //<-Manuial numbers break; case 6: //rotate right ctx.scale(scaleRatio,scaleRatio); ctx.rotate(90*Math.PI/180); ctx.translate(-462,-130); //<-Manuial numbers break; } //re-translate and draw image //ctx.translate(-halfScaledWidth,-halfScaledheight); ctx.drawImage(image,-halfScaledWidth, -halfScaledheight); ctx.restore(); //<- RESTORE } /* Rotate Image */ } 

J'ai l'orientation définie manuellement afin que je puisse voir comment il semble dans chaque position je m'inquiète. Si c'est une orientation portrait, je tourne le canevas.

J'ai essayé save () et restore (). J'ai essayé de traduire (x, y) puis de traduire (-x, -y). Ma supposition est que, en raison de l'échelle, la grille est désactivée et x et y doivent être multipliés. J'ai essayé de faire cela contre l'échelle et je n'ai toujours pas fonctionné.

Comme vous pouvez le voir, je configure manuellement la traduction, mais cela ne fonctionne qu'avec la taille d'image avec laquelle je travaille, donc pas une bonne solution!

Voici le code: JSFiddle Si je fais une rotation normale, tout fonctionne.

Merci!

Transformations

Pour la réponse facile, si vous n'êtes pas intéressé par la façon de passer au bas où vous trouverez une solution de rechange à votre problème. Tout est commenté. J'ai fait une bonne idée de ce que vous vouliez.

Si vous êtes intéressé par ce que je considère comme un moyen plus simple d'utiliser les fonctions de transformation 2D, lisez le reste.

Mathématiques matricielles

Lorsque vous utilisez la traduction, l'échelle et la rotation via l'API 2D de la toile, que vous faites, multipliez la matrice existante par celle créée avec chaque fonction.

Fondamentalement, quand vous faites

 ctx.rotate(Math.PI); // rotate 180 deg 

L'API crée une nouvelle matrice de rotation et multiplie la matrice existante avec elle.

Contrairement à la multiplication matricielle mathématique, la multiplication matricielle changera le résultat en fonction de l'ordre que vous multipliez. Dans la multiplication mathématique normale A * B = B * A mais cela ne tient pas vrai pour les matrices mA * mB != mB * mA (notez que non égal)

Cela devient plus problématique lorsque vous devez appliquer plusieurs transformations différentes.

 ctx.scale(2,2); ctx.rotate(Math.PI/2); ctx.translate(100,100); 

Ne donne pas le même résultat que

 ctx.scale(2,2); ctx.translate(100,100); ctx.rotate(Math.PI/2); 

L'ordre que vous devez appliquer aux tranforms dépend de ce que vous essayez d'atteindre. L'utilisation de l'API de cette façon est très pratique pour les animations liées complexes. Malheureusement, c'est aussi une source de frustration sans fin si vous ne connaissez pas les mathématiques matricielles. Cela oblige également beaucoup à utiliser les fonctions de save et de restore pour restaurer la transformation par défaut, qui, dans certaines situations, peut être très coûteux dans les performances du GPU.

SetTransform ()

Nous avons de la chance alors que l'API 2D a également la fonction ctx.setTransform(a, b, c, d, e, f) ce qui est tout ce dont vous auriez vraiment besoin. Cette fonction remplace la transformation existante par celle fournie. La plupart de la documentation est assez vague quant à la signification de a,b,c,d,e,f mais contenue dans ceux-ci, c'est la rotation, l'échelle et la traduction.

Une utilisation pratique de la fonction est de configurer la transformation par défaut plutôt que d'utiliser la sauvegarde et la restauration.

Je vois beaucoup ce genre de chose. (L'exemple 1, référencé plus bas)

 // transform for image 1 ctx.save(); // save state ctx.scale(2,2); ctx.rotate(Math.PI/2); ctx.translate(100,100); // draw the image ctx.drawImage(img1, -img1.width / 2, -img1.height / 2); ctx.restore(); // restore saved state // transform for image 2 ctx.save(); // save state agian ctx.scale(1,1); ctx.rotate(Math.PI); ctx.translate(100,100); // draw the image ctx.drawImage(img2, -img2.width / 2, -img2.height / 2); ctx.restore(); // restore saved state 

Un moyen plus simple est simplement de laisser tomber les sauvegardes et les restaurer et réinitialiser la transformation manuellement en la configurant dans la matrice Identité

 ctx.scale(2,2); ctx.rotate(Math.PI/2); ctx.translate(100,100); // draw the image ctx.drawImage(img1, -img1.width / 2, -img1.height / 2); ctx.setTransform(1,0,0,1,0,0); // restore default transform // transform for image 2 ctx.scale(1,1); ctx.rotate(Math.PI); ctx.translate(100,100); // draw the image ctx.drawImage(img2, -img2.width / 2, -img2.height / 2); ctx.setTransform(1,0,0,1,0,0); // restore default transform 

Maintenant, je suis sûr que vous vous demandez toujours ce que ces chiffres sont transmis à setTransform et à ce qu'ils veulent dire?

La façon la plus simple de se souvenir d'eux est comme 2 vecteurs et 1 coordonnée. Les deux vecteurs décrivent la direction et l'échelle d'un seul pixel, la coordonnée est simplement l'emplacement pixel x, y de l'origine (l'emplacement que le dessin à 0,0 sera sur le canevas).

Un pixel et son axe

Imaginez un seul pixel, c'est le pixel transformé abstraite qui peut être mis à l'échelle et tourné par la transformation actuelle. Il a deux axes, X et Y. Pour décrire chaque axe, nous avons besoin de deux nombres (un vecteur), ce qui décrit la direction et l'échelle de l'écran (non transformés) des côtés supérieur et gauche du pixel. Donc, pour un pixel normal qui correspond aux pixels de l'écran, l'axe X se trouve en haut de gauche à droite et a un pixel. Le vecteur est ( 1,0 ) un pixel à travers, pas de pixels en bas. Pour l'axe Y qui descend sur l'écran, le vecteur est ( 0,1 ) pas de pixels, un pixel vers le bas. L'origine est le pixel d'écran supérieur droit qui est à la coordonnée ( 0,0 ).

Ainsi, nous obtenons la Matrice d'Identité, la matrice par défaut pour l'API 2D (et plusieurs autres API) L'axe X ( 1,0 ), l'axe Y ( 0,1 ) et l'origine ( 0,0 ) qui correspondent aux six arguments pour setTransform(1,0,0,1,0,0) .

Maintenant, disons que nous voulons agrandir le pixel. Tout ce que nous faisons est d'augmenter la taille des axes X et Y setTransform(2,0,0,2,0,0) est identique à l' scale(2,2) (à partir de la transformée par défaut) Le haut de notre pixel est maintenant de deux pixels Long sur le dessus et deux pixels de long sur le côté gauche. Pour redimensionner setTransform(0.5,0,0,0.5,0,0) notre pixel est maintenant un demi-pixel à travers et vers le bas.

Ces deux vecteurs d'axe (a, b) & (c, d) peuvent pointer dans n'importe quelle direction, sont complètement indépendants l'un de l'autre, ils ne doivent pas être à 90 degrés l'un par rapport à l'autre, de sorte qu'ils peuvent fausser le pixel, et ils ne doivent pas non plus Exigent qu'ils soient de même longueur afin que vous puissiez modifier l'aspect des pixels. L'origine est également indépendante et n'est que la toile des coordonnées absolues en pixels de l'origine et peut être configurée n'importe où sur ou hors du canevas.

Dites maintenant que nous voulons faire pivoter la transformée 90Deg dans le sens des aiguilles d'une montre, augmenter les deux axes par 2 et positionner l'origine au centre de la toile. Nous voulons que l'axe des X (en haut) de notre pixel soit de 2 pixels de long et qui pointe vers le bas vers le bas de l'écran. Le vecteur est ( 0,2 ) 0 en travers et deux vers le bas. Nous voulons que le côté gauche de notre pixel atteigne 2 longs et pointe vers la gauche de l'écran ( -2,0 ) Deux fois négatifs et aucun vers le bas. Et l'origine au centre est ( canvas.width / 2, canvas.height / 2 ) pour obtenir la matrice finale qui est setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)

La rotation de l'autre manière est setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)

Easy Rotate 90deg

Vous remarquerez peut-être que la rotation de 90 degrés consiste à échanger les vecteurs et à changer de signe.

  • Le vecteur ( x,y ) tourné de 90 degrés dans le sens des aiguilles d'une montre est ( -y,x ).
  • Le vecteur ( x,y ) tourné de 90 degrés dans le sens inverse des aiguilles d'une montre est ( y,-x ).

Échangez les x et y et annulez le y pour le sens des aiguilles d'une montre ou annulez le x pour la rotation dans le sens inverse des aiguilles d'une montre.

Pour 180, il commence à 0 deg vector ( 1,0 )

 // input vector var x = 1; var y = 0; // rotated vector var rx90 = -y; // swap y to x and make it negative var ry90 = x; // x to y as is // rotate again same thing var rx180 = -ry90; var rx180 = rx90; // Now for 270 var rx270 = -ry180; // swap y to x and make it negative var rx270 = rx180; 

Ou tout en termes de simplement x et y

  • 0 deg ( x,y )
  • 90deg ( -y,x )
  • 180deg ( -x,-y )
  • 270deg ( y,-x )
  • Et retour à 360 ( x,y ).

C'est un attribut très pratique d'un vecteur que nous pouvons exploiter pour simplifier la création de notre matrice de transformation. Dans la plupart des situations, nous ne voulons pas égarer notre image; nous savons donc que l'axe des Y est toujours 90Deg dans le sens des aiguilles d'une montre à partir de l'axe des x. Maintenant, il suffit de décrire l'axe des x et en appliquant la rotation de 90 ° à ce vecteur, nous avons l'axe y.

Donc, les vars x et y sont l'échelle et la direction du haut de notre pixel (axe x), le ox , c'est l'emplacement de l'origine sur la toile (traduction).

 var x = 1; // one pixel across var y = 0; // none down var ox = canvas.width / 2; // center of canvas var oy = canvas.height / 2; 

Maintenant, créer la transformation est

 ctx.setTransform(x, y, -y, x, ox, oy); 

Notez que l'axe y est à 90 degrés par rapport à l'axe des x.

Trig and the Unit vector

Tout bien et facile lorsque l'axe est aligné sur le haut et les côtés, comment obtenez-vous le vecteur pour un axe à un angle arbitraire tel que fourni par l'argument pour ctx.rotate(angle) Pour cela, nous avons besoin d'un petit peu de trigonométrie. La fonction Math Math.cos(angle) renvoie la composante x de l'angle, l'angle et Math.sin(angle) nous donne le composant Y. Pour zero deg cos(0) = 1 et sin(0) = 0 pour 90 degrés ( Math.PI/2 radians) cos(PI/2) = 0 et sin(PI/2) = 1 .

La beauté de l'utilisation du péché et de la cos est que les deux nombres que nous obtenons pour notre vecteur nous donnent toujours un vecteur de 1 unité (pixel) long (c'est ce qu'on appelle un vecteur normalisé ou un vecteur unitaire) donc cos (a) 2 + Sin (a) 2 = 1

Pourquoi cela importe-t-il? Car il rend la mise à l'échelle très simple. En supposant que nous gardons toujours l'aspect carré, nous avons besoin d'un seul numéro pour l'échelle. Pour dimensionner un vecteur, il suffit de le multiplier par l'échelle

 var scale = 2; // scale of 2 var ang = Math.random() * 100; // any random angle var x = Math.cos(ang); // get the x axis as a unit vector. var y = Math.sin(ang); // scale the axis x *= scale; y *= scale; 

Le vecteur x, y a maintenant deux unités de long.

Mieux vaut utiliser, sauvegarder, restaurer, faire pivoter, moduler, traduire … 🙁

Maintenant, mettre tout ensemble pour créer une matrice avec une rotation arbitraire, une échelle et une traduction (origine)

 // ctx is the 2D context, // originX, and originY is the origin, same as ctx.translate(originX,originY) // rotation is the angle in radians same as ctx.rotate(rotation) // scale is the scale of x and y axis same as ctx.scale(scale,scale) function createTransform(ctx,originX,originY,rotation,scale){ var x, y; x = Math.cos(rotation) * scale; y = Math.sin(rotation) * scale; ctx.setTransform(x, y, -y, x, originX, originY); } 

Maintenant, appliquez cela à l' exemple (1) donné ci-dessus

 // dont need ctx.save(); // save state // dont need ctx.scale(2,2); // dont need ctx.rotate(Math.PI/2); // dont need ctx.translate(100,100); createMatrix(ctx, 100, 100, Math.PI/2, 2) // draw the image normally ctx.drawImage(img1, -img1.width / 2, -img1.height / 2); // dont need ctx.restore(); // restore saved state // transform for image 2 // dont need ctx.save(); // save state agian // dont need ctx.scale(1,1); // dont need ctx.rotate(Math.PI); // dont need ctx.translate(100,100); // we don't have to reset the default transform because // ctx.setTransform completely replaces the current transform createMatrix(ctx, 100, 100, Math.PI/2, 2) // draw the image ctx.drawImage(img2, -img2.width / 2, -img2.height / 2); // dont need ctx.restore(); // restore saved state 

Et c'est ainsi que vous utilisez setTransform pour simplifier la transformation de la toile, plutôt que de deviner, d'essayer et d'échouer, d'étaler, de tourner et de traduire en va-et-vient dans une marge de sauvegarde et de restauration.

En utilisant cela pour simplifier votre code

La réponse

Et maintenant à votre question

Je ne suis pas tout à fait sûr de ce que vous êtes après, je présume que vous n'avez pas l'intention d'agrandir la toile pour accueillir l'image, que l'image est toujours au centre et que l'aspect reste le même. Comme les rotations sont alignées sur l'écran, je configurerai les transformées manuellement

  // this is in set up code const MAX_SIZE_WIDTH = 640; const MAX_SIZE_HEIGHT = 480; orientationData = []; orientationData[6] = Math.PI/2; // xAxis pointing down orientationData[8] = -Math.PI/2; // xAxis pointing up orientationData[3] = Math.PI; //xAxis pointing to left // in your code var orient,w,h,iw,ih,scale,ax,ay; // w and h are canvas size // assume image is the loaded image iw = image.width; // get the image width and height so I dont have to type as much. ih = image.height; if(orientation != 1){ var orient = orientationData[orientation]; if(orient === undefined){ return; // bad data so return } // get scale and resize canvas to suit // is the image on the side if(orientation === 6 || orientation === 8){ // on side so swap width and height // get the height and width scales for the image, dont scale // if the image is smaller than the dimension scale = Math.min(1, MAX_SIZE_WIDTH / ih, MAX_SIZE_HEIGHT / iw ); w = canvas.width = scale * ih; h = canvas.height = scale * iw; }else{ // for normal orientation scale = Math.min(1, MAX_SIZE_WIDTH / iw, MAX_SIZE_HEIGHT / ih ); h = canvas.height = scale * ih; w = canvas.width = scale * iw; } // Do you really need to clear the canvas if the image is filling it?? // ensure that the default transform is set ctx.setTransform(1, 0, 0, 1, 0, 0); // clear the canvas ctx.clearRect(0, 0, w, h); // now create the transformation matrix to // position the image in the center of the screen // first get the xAxis and scale it ax = Math.cos(orient) * scale; ay = Math.sin(orient) * scale; // now set the transform, the origin is always the canvas center // and the Y axis is 90 deg clockwise from the xAxis and same scale ctx.setTransform(ax, ay, -ay, ax, w / 2, h / 2); // now draw the image offset by half its width and height // so that it is centered on the canvas ctx.drawImage(image,-iw / 2, -ih / 2); // restore the default transform ctx.setTransform(1, 0, 0, 1, 0, 0); } // done. 

Essayez celui-ci https://jsfiddle.net/uLdf4paL/2/ . Votre code est correct, mais vous devez modifier la variable d' orientation lorsque vous essayez de faire pivoter et d'étaler l'image (si c'est ce que vous voulez obtenir).