Acercar un punto (usando escalar y traducir)


156

Quiero poder acercar el punto debajo del mouse en un lienzo HTML 5, como hacer zoom en Google Maps . ¿Cómo puedo lograr eso?


2
¡Utilicé esto para hacer zoom en mi lienzo y funciona muy bien! Lo único que tengo que agregar es que el cálculo de la cantidad de zoom no es el esperado. "var zoom = 1 + rueda / 2;" es decir, esto da como resultado 1.5 para acercar y 0.5 para alejar. Edité esto en mi versión para que tenga 1.5 para acercar y 1 / 1.5 para alejar, lo que hace que la cantidad de acercar y alejar sea igual. Por lo tanto, si amplía una vez y retrocede, tendrá la misma imagen que antes del zoom.
Chris

2
Tenga en cuenta que esto no funciona en Firefox, pero el método se puede aplicar fácilmente al complemento jQuery mousewheel . ¡Gracias por compartir!
johndodo

2
zoom var = Math.pow (1.5f, rueda); // Use esto para calcular el zoom. Tiene la ventaja de que hacer zoom por rueda = 2 es lo mismo que hacer zoom dos veces por rueda = 1. Además, acercar +2 y alejar +2 restaura la escala original.
Matt

Respuestas:


126

La mejor solución es simplemente mover la posición de la ventana gráfica en función del cambio en el zoom. El punto de zoom es simplemente el punto en el zoom anterior y el nuevo zoom que desea que permanezca igual. Es decir, la vista ampliada previamente y la vista ampliada tienen el mismo punto de zoom relativo a la vista. Dado que estamos escalando en relación con el origen. Puede ajustar la posición de la vista en consecuencia:

scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);

Entonces, realmente puedes desplazarte hacia abajo y hacia la derecha cuando acercas, por un factor de cuánto acercaste, en relación con el punto en el que hiciste zoom.

ingrese la descripción de la imagen aquí


2
Más valioso que el código de cortar y pegar es la explicación de cuál es la mejor solución y por qué funciona sin el equipaje, especialmente si tiene tres líneas de largo.
Tatarizar el

2
scalechange = newscale / oldscale?
Tejesh Alimilli

44
Además, me gustaría agregar para aquellos que buscan lograr un mapa como el componente de zoom panorámico, que el mouse X, Y debe ser (mousePosRelativeToContainer - currentTransform) / currentScale, de lo contrario, tratará la posición actual del mouse como relativa al contenedor.
Gilad

1
Sí, esta matemática supone que tanto el zoom como la panorámica se encuentran en las coordenadas relevantes para el origen. Si son relativos a la ventana gráfica, debe ajustarlos adecuadamente. Aunque supongo que la matemática correcta es zoomPoint = (mousePosRelativeToContainer + currentTranslation). Esta matemática también supone que el punto de origen está típicamente en la esquina superior izquierda del campo. Pero, adaptarse a situaciones ligeramente atípicas es mucho más fácil dada la simplicidad.
Tatarizar

1
@ C.Finke La segunda forma de hacerlo es mediante el uso de las traducciones dentro del ctx. Dibujas todo del mismo tamaño y en las mismas posiciones. Pero, simplemente usa las multiplicaciones matriciales dentro del lienzo de JavaScript para establecer la panorámica y la escala (zoom) del contexto. Entonces, en lugar de volver a dibujar todas las formas en una ubicación diferente. Los dibujas en el mismo lugar y mueves la ventana gráfica dentro de javascript. Este método también requeriría que tome los eventos del mouse y los traduzca al revés. Entonces restarías la panorámica y luego invertirías el factor con el zoom.
Tatarizar

68

Finalmente lo resolvió:

var zoomIntensity = 0.2;

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = 600;
var height = 200;

var scale = 1;
var originx = 0;
var originy = 0;
var visibleWidth = width;
var visibleHeight = height;


function draw(){
    // Clear screen to white.
    context.fillStyle = "white";
    context.fillRect(originx,originy,800/scale,600/scale);
    // Draw the black square.
    context.fillStyle = "black";
    context.fillRect(50,50,100,100);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);

canvas.onwheel = function (event){
    event.preventDefault();
    // Get mouse offset.
    var mousex = event.clientX - canvas.offsetLeft;
    var mousey = event.clientY - canvas.offsetTop;
    // Normalize wheel to +1 or -1.
    var wheel = event.deltaY < 0 ? 1 : -1;

    // Compute zoom factor.
    var zoom = Math.exp(wheel*zoomIntensity);
    
    // Translate so the visible origin is at the context's origin.
    context.translate(originx, originy);
  
    // Compute the new visible origin. Originally the mouse is at a
    // distance mouse/scale from the corner, we want the point under
    // the mouse to remain in the same place after the zoom, but this
    // is at mouse/new_scale away from the corner. Therefore we need to
    // shift the origin (coordinates of the corner) to account for this.
    originx -= mousex/(scale*zoom) - mousex/scale;
    originy -= mousey/(scale*zoom) - mousey/scale;
    
    // Scale it (centered around the origin due to the trasnslate above).
    context.scale(zoom, zoom);
    // Offset the visible origin to it's proper position.
    context.translate(-originx, -originy);

    // Update scale and others.
    scale *= zoom;
    visibleWidth = width / scale;
    visibleHeight = height / scale;
}
<canvas id="canvas" width="600" height="200"></canvas>

La clave, como señaló @Tatarize , es calcular la posición del eje de manera que el punto de zoom (puntero del mouse) permanezca en el mismo lugar después del zoom.

Originalmente el mouse está a una distancia mouse/scalede la esquina, queremos que el punto debajo del mouse permanezca en el mismo lugar después del zoom, pero esto está mouse/new_scalelejos de la esquina. Por lo tanto, necesitamos cambiar las origin(coordenadas de la esquina) para dar cuenta de esto.

originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom

El código restante debe aplicar la escala y traducir al contexto de dibujo para que su origen coincida con la esquina del lienzo.


Gracias amigo, casi perdí 2 días, antes de encontrar su código
Velaro

¡Hey, solo estaba buscando algo como esto y solo quería decir que lo avivó!
Chrisrisick

26

Este es en realidad un problema muy difícil (matemáticamente), y estoy trabajando en lo mismo casi. Hice una pregunta similar en Stackoverflow pero no obtuve respuesta, pero publiqué en DocType (StackOverflow para HTML / CSS) y obtuve una respuesta. Échale un vistazo http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example

Estoy en medio de la construcción de un complemento jQuery que hace esto (zoom de estilo de Google Maps usando transformaciones CSS3). Tengo el zoom en el bit del cursor del mouse funcionando bien, aún tratando de descubrir cómo permitir que el usuario arrastre el lienzo como lo puede hacer en Google Maps. Cuando lo haga funcionar, publicaré el código aquí, pero revise el enlace de arriba para la parte del mouse-zoom-to-point.

No me di cuenta de que había métodos de escala y traducción en el contexto de Canvas, puedes lograr lo mismo usando CSS3, por ejemplo. usando jQuery:

$('div.canvasContainer > canvas')
    .css('-moz-transform', 'scale(1) translate(0px, 0px)')
    .css('-webkit-transform', 'scale(1) translate(0px, 0px)')
    .css('-o-transform', 'scale(1) translate(0px, 0px)')
    .css('transform', 'scale(1) translate(0px, 0px)');

Asegúrese de establecer el CSS3 transform-origin en 0, 0 (-moz-transform-origin: 0 0). El uso de CSS3 transform le permite acercar cualquier cosa, solo asegúrese de que el contenedor DIV esté configurado para desbordarse: oculto para evitar que los bordes con zoom se derramen por los lados.

Depende de usted si utiliza transformaciones CSS3, o los métodos de escala y traducción propios del lienzo, pero consulte el enlace anterior para ver los cálculos.


Actualización: Meh! Voy a publicar el código aquí en lugar de hacer que sigas un enlace:

$(document).ready(function()
{
    var scale = 1;  // scale of the image
    var xLast = 0;  // last x location on the screen
    var yLast = 0;  // last y location on the screen
    var xImage = 0; // last x location on the image
    var yImage = 0; // last y location on the image

    // if mousewheel is moved
    $("#mosaicContainer").mousewheel(function(e, delta)
    {
        // find current location on screen 
        var xScreen = e.pageX - $(this).offset().left;
        var yScreen = e.pageY - $(this).offset().top;

        // find current location on the image at the current scale
        xImage = xImage + ((xScreen - xLast) / scale);
        yImage = yImage + ((yScreen - yLast) / scale);

        // determine the new scale
        if (delta > 0)
        {
            scale *= 2;
        }
        else
        {
            scale /= 2;
        }
        scale = scale < 1 ? 1 : (scale > 64 ? 64 : scale);

        // determine the location on the screen at the new scale
        var xNew = (xScreen - xImage) / scale;
        var yNew = (yScreen - yImage) / scale;

        // save the current screen location
        xLast = xScreen;
        yLast = yScreen;

        // redraw
        $(this).find('div').css('-moz-transform', 'scale(' + scale + ')' + 'translate(' + xNew + 'px, ' + yNew + 'px' + ')')
                           .css('-moz-transform-origin', xImage + 'px ' + yImage + 'px')
        return false;
    });
});

Por supuesto, deberá adaptarlo para usar la escala del lienzo y los métodos de traducción.


Actualización 2: Acabo de notar que estoy usando transform-origin junto con translate. Me las arreglé para implementar una versión que solo usa la escala y la traducción por su cuenta, compruébelo aquí http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Espere a que se descarguen las imágenes y luego use su rueda del mouse para hacer zoom, también es compatible con la panorámica arrastrando la imagen. Está utilizando transformaciones CSS3, pero debería poder usar los mismos cálculos para su lienzo.


finalmente lo resolví, me tomó 3 minutos ahora después de aproximadamente 2 semanas de hacer otra cosa
csiz

El enlace @Synday Ironfoot en su actualización no funciona. Este enlace: dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html quiero esta impelementación. ¿Puedes publicar aquí el código? gracias
Bogz

2
a partir de hoy (septiembre de 2014) el enlace a MosaicTest.html está inactivo.
Chris

la demostración de mosaico se ha ido. Yo uso vanilla js generalmente y no jQuery. ¿A qué se refiere $ (esto)? the document.body.offsetTop? Realmente quiero ver la demostración de mosaico que mi proyecto foreverscape.com realmente podría beneficiarse de él.
FlavorScape

2
La página de demostración del mosaico se guarda en archive.org: web.archive.org/web/20130126152008/http://…
Chris

9

Me encontré con este problema usando c ++, que probablemente no debería haber tenido, solo comencé a usar matrices OpenGL para empezar ... de todos modos, si estás usando un control cuyo origen es la esquina superior izquierda, y quieres paneo / zoom Al igual que Google Maps, aquí está el diseño (usando Allegro como mi controlador de eventos):

// initialize
double originx = 0; // or whatever its base offset is
double originy = 0; // or whatever its base offset is
double zoom = 1;

.
.
.

main(){

    // ...set up your window with whatever
    //  tool you want, load resources, etc

    .
    .
    .
    while (running){
        /* Pan */
        /* Left button scrolls. */
        if (mouse == 1) {
            // get the translation (in window coordinates)
            double scroll_x = event.mouse.dx; // (x2-x1) 
            double scroll_y = event.mouse.dy; // (y2-y1) 

            // Translate the origin of the element (in window coordinates)      
            originx += scroll_x;
            originy += scroll_y;
        }

        /* Zoom */ 
        /* Mouse wheel zooms */
        if (event.mouse.dz!=0){    
            // Get the position of the mouse with respect to 
            //  the origin of the map (or image or whatever).
            // Let us call these the map coordinates
            double mouse_x = event.mouse.x - originx;
            double mouse_y = event.mouse.y - originy;

            lastzoom = zoom;

            // your zoom function 
            zoom += event.mouse.dz * 0.3 * zoom;

            // Get the position of the mouse
            // in map coordinates after scaling
            double newx = mouse_x * (zoom/lastzoom);
            double newy = mouse_y * (zoom/lastzoom);

            // reverse the translation caused by scaling
            originx += mouse_x - newx;
            originy += mouse_y - newy;
        }
    }
}  

.
.
.

draw(originx,originy,zoom){
    // NOTE:The following is pseudocode
    //          the point is that this method applies so long as
    //          your object scales around its top-left corner
    //          when you multiply it by zoom without applying a translation.

    // draw your object by first scaling...
    object.width = object.width * zoom;
    object.height = object.height * zoom;

    //  then translating...
    object.X = originx;
    object.Y = originy; 
}

9

Me gusta la respuesta de Tatarize , pero proporcionaré una alternativa. Este es un problema trivial de álgebra lineal, y el método que presento funciona bien con panorámica, zoom, sesgo, etc. Es decir, funciona bien si su imagen ya está transformada.

Cuando se escala una matriz, la escala está en el punto (0, 0). Entonces, si tiene una imagen y la escala en un factor de 2, el punto inferior derecho se duplicará en las direcciones x e y (usando la convención de que [0, 0] es la esquina superior izquierda de la imagen).

Si, en cambio, desea ampliar la imagen sobre el centro, entonces la solución es la siguiente: (1) traduzca la imagen de modo que su centro esté en (0, 0); (2) escalar la imagen por factores x e y; (3) traduzca la imagen de nuevo. es decir

myMatrix
  .translate(image.width / 2, image.height / 2)    // 3
  .scale(xFactor, yFactor)                         // 2
  .translate(-image.width / 2, -image.height / 2); // 1

Más abstracto, la misma estrategia funciona para cualquier punto. Si, por ejemplo, desea escalar la imagen en un punto P:

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y);

Y, por último, si la imagen ya está transformada de alguna manera (por ejemplo, si está rotada, sesgada, traducida o escalada), entonces la transformación actual necesita ser preservada. Específicamente, la transformación definida anteriormente necesita ser multiplicada posteriormente (o multiplicada a la derecha) por la transformación actual.

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y)
  .multiply(myMatrix);

Ahí tienes. Aquí hay un plunk que muestra esto en acción. Desplácese con la rueda del mouse sobre los puntos y verá que permanecen consistentemente. (Probado solo en Chrome.) Http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview


1
Debo decir que si tiene disponible una matriz de transformación afín, úsela con entusiasmo. Muchas matrices de transformación incluso tendrán funciones de zoom (sx, sy, x, y) que hacen exactamente eso. Casi vale la pena cocinar uno si no le dan uno para usar.
Tatarizar el

De hecho, confieso que en el código en el que utilicé esta solución, desde entonces se ha reemplazado con una clase de matriz. Y he hecho esto exactamente varias veces y he preparado clases matriciales no menos de dos veces. ( github.com/EmbroidePy/pyembroidery/blob/master/pyembroidery/… ), ( github.com/EmbroidePy/EmbroidePy/blob/master/embroidepy/… ). Si desea algo más complejo que exactamente estas operaciones, una matriz es básicamente la respuesta correcta y una vez que tiene un control del álgebra lineal, se da cuenta de que esta respuesta es en realidad la mejor respuesta.
Tatarize

6

Aquí está mi solución para una imagen orientada al centro:

var MIN_SCALE = 1;
var MAX_SCALE = 5;
var scale = MIN_SCALE;

var offsetX = 0;
var offsetY = 0;

var $image     = $('#myImage');
var $container = $('#container');

var areaWidth  = $container.width();
var areaHeight = $container.height();

$container.on('wheel', function(event) {
    event.preventDefault();
    var clientX = event.originalEvent.pageX - $container.offset().left;
    var clientY = event.originalEvent.pageY - $container.offset().top;

    var nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale - event.originalEvent.deltaY / 100));

    var percentXInCurrentBox = clientX / areaWidth;
    var percentYInCurrentBox = clientY / areaHeight;

    var currentBoxWidth  = areaWidth / scale;
    var currentBoxHeight = areaHeight / scale;

    var nextBoxWidth  = areaWidth / nextScale;
    var nextBoxHeight = areaHeight / nextScale;

    var deltaX = (nextBoxWidth - currentBoxWidth) * (percentXInCurrentBox - 0.5);
    var deltaY = (nextBoxHeight - currentBoxHeight) * (percentYInCurrentBox - 0.5);

    var nextOffsetX = offsetX - deltaX;
    var nextOffsetY = offsetY - deltaY;

    $image.css({
        transform : 'scale(' + nextScale + ')',
        left      : -1 * nextOffsetX * nextScale,
        right     : nextOffsetX * nextScale,
        top       : -1 * nextOffsetY * nextScale,
        bottom    : nextOffsetY * nextScale
    });

    offsetX = nextOffsetX;
    offsetY = nextOffsetY;
    scale   = nextScale;
});
body {
    background-color: orange;
}
#container {
    margin: 30px;
    width: 500px;
    height: 500px;
    background-color: white;
    position: relative;
    overflow: hidden;
}
img {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    max-width: 100%;
    max-height: 100%;
    margin: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div id="container">
    <img id="myImage" src="http://s18.postimg.org/eplac6dbd/mountain.jpg">
</div>


4

Aquí hay una forma alternativa de hacerlo que usa setTransform () en lugar de scale () y translate (). Todo se almacena en el mismo objeto. Se supone que el lienzo está en 0,0 en la página, de lo contrario, deberá restar su posición de los códigos de la página.

this.zoomIn = function (pageX, pageY) {
    var zoomFactor = 1.1;
    this.scale = this.scale * zoomFactor;
    this.lastTranslation = {
        x: pageX - (pageX - this.lastTranslation.x) * zoomFactor,
        y: pageY - (pageY - this.lastTranslation.y) * zoomFactor
    };
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    this.lastTranslation.x,
                                    this.lastTranslation.y);
};
this.zoomOut = function (pageX, pageY) {
    var zoomFactor = 1.1;
    this.scale = this.scale / zoomFactor;
    this.lastTranslation = {
        x: pageX - (pageX - this.lastTranslation.x) / zoomFactor,
        y: pageY - (pageY - this.lastTranslation.y) / zoomFactor
    };
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    this.lastTranslation.x,
                                    this.lastTranslation.y);
};

Código de acompañamiento para manejar la panorámica:

this.startPan = function (pageX, pageY) {
    this.startTranslation = {
        x: pageX - this.lastTranslation.x,
        y: pageY - this.lastTranslation.y
    };
};
this.continuePan = function (pageX, pageY) {
    var newTranslation = {x: pageX - this.startTranslation.x,
                          y: pageY - this.startTranslation.y};
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    newTranslation.x, newTranslation.y);
};
this.endPan = function (pageX, pageY) {
    this.lastTranslation = {
        x: pageX - this.startTranslation.x,
        y: pageY - this.startTranslation.y
    };
};

Para obtener la respuesta usted mismo, considere que las mismas coordenadas de la página deben coincidir con las mismas coordenadas del lienzo antes y después del zoom. Entonces puedes hacer algo de álgebra a partir de esta ecuación:

(pageCoords - traducción) / scale = canvasCoords


3

Quiero poner aquí información para aquellos que hacen un dibujo separado de la imagen y la mueven para acercarla.

Esto puede ser útil cuando desea almacenar los zooms y la posición de la ventana gráfica.

Aquí está el cajón:

function redraw_ctx(){
   self.ctx.clearRect(0,0,canvas_width, canvas_height)
   self.ctx.save()
   self.ctx.scale(self.data.zoom, self.data.zoom) // 
   self.ctx.translate(self.data.position.left, self.data.position.top) // position second
   // Here We draw useful scene My task - image:
   self.ctx.drawImage(self.img ,0,0) // position 0,0 - we already prepared
   self.ctx.restore(); // Restore!!!
}

La escala de aviso DEBE ser la primera .

Y aquí está el zoomer:

function zoom(zf, px, py){
    // zf - is a zoom factor, which in my case was one of (0.1, -0.1)
    // px, py coordinates - is point within canvas 
    // eg. px = evt.clientX - canvas.offset().left
    // py = evt.clientY - canvas.offset().top
    var z = self.data.zoom;
    var x = self.data.position.left;
    var y = self.data.position.top;

    var nz = z + zf; // getting new zoom
    var K = (z*z + z*zf) // putting some magic

    var nx = x - ( (px*zf) / K ); 
    var ny = y - ( (py*zf) / K);

    self.data.position.left = nx; // renew positions
    self.data.position.top = ny;   
    self.data.zoom = nz; // ... and zoom
    self.redraw_ctx(); // redraw context
    }

y, por supuesto, necesitaríamos un arrastrador:

this.my_cont.mousemove(function(evt){
    if (is_drag){
        var cur_pos = {x: evt.clientX - off.left,
                       y: evt.clientY - off.top}
        var diff = {x: cur_pos.x - old_pos.x,
                    y: cur_pos.y - old_pos.y}

        self.data.position.left += (diff.x / self.data.zoom);  // we want to move the point of cursor strictly
        self.data.position.top += (diff.y / self.data.zoom);

        old_pos = cur_pos;
        self.redraw_ctx();

    }


})

3
if(wheel > 0) {
    this.scale *= 1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1.1 - 1);
    this.offsetY -= (mouseY - this.offsetY) * (1.1 - 1);
}
else {
    this.scale *= 1/1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1/1.1 - 1);
    this.offsetY -= (mouseY - this.offsetY) * (1/1.1 - 1);
}

2

Aquí hay una implementación de código de la respuesta de @ tatarize, usando PIXI.js. Tengo una ventana gráfica que mira parte de una imagen muy grande (por ejemplo, el estilo de Google Maps).

$canvasContainer.on('wheel', function (ev) {

    var scaleDelta = 0.02;
    var currentScale = imageContainer.scale.x;
    var nextScale = currentScale + scaleDelta;

    var offsetX = -(mousePosOnImage.x * scaleDelta);
    var offsetY = -(mousePosOnImage.y * scaleDelta);

    imageContainer.position.x += offsetX;
    imageContainer.position.y += offsetY;

    imageContainer.scale.set(nextScale);

    renderer.render(stage);
});
  • $canvasContainer es mi contenedor html
  • imageContainer es mi contenedor PIXI que tiene la imagen en él.
  • mousePosOnImage es la posición del mouse con respecto a la imagen completa (no solo el puerto de vista).

Así es como obtuve la posición del mouse:

  imageContainer.on('mousemove', _.bind(function(ev) {
    mousePosOnImage = ev.data.getLocalPosition(imageContainer);
    mousePosOnViewport.x = ev.data.originalEvent.offsetX;
    mousePosOnViewport.y = ev.data.originalEvent.offsetY;
  },self));

0

Debe obtener el punto en el espacio mundial (en oposición al espacio de la pantalla) antes y después del zoom, y luego traducir por el delta.

mouse_world_position = to_world_position(mouse_screen_position);
zoom();
mouse_world_position_new = to_world_position(mouse_screen_position);
translation += mouse_world_position_new - mouse_world_position;

La posición del mouse está en el espacio de la pantalla, por lo que debe transformarla en espacio mundial. La transformación simple debería ser similar a esto:

world_position = screen_position / scale - translation

0

puede usar la función scrollto (x, y) para manejar la posición de la barra de desplazamiento hasta el punto que necesita que se muestre después del zoom. para encontrar la posición del mouse, use event.clientX y event.clientY. Esto te ayudara


0

Una cosa importante ... si tienes algo como:

body {
  zoom: 0.9;
}

Necesitas hacer lo mejor en el lienzo:

canvas {
  zoom: 1.1;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.