introducción a la graficación por computadora unidad ii
TRANSCRIPT
Introducción a la Graficación por Computadora
Unidad II
Transformaciones
Fundamentos de programación Gráfica
Java y la programación orientada a objetos
Programación Orientada a Objetos Java soporta el enfoque de Programación Orientada a Objetos
(OOP). OOP se enfoque en el diseño de los tipos de datos más que los
algoritmos denominados objetos y clases. ¿Por qué esta es una distinción importante?
Los objetos son sujetos y los algoritmos son los verbos.
¿Por qué es una tarea más fácl? Se identifican las acciones que se aplican a un sujeto dado Se identifican esos objetos con las acciones que le pueden ser aplicadas.
¿Por qué es más fácil? Identificando clases de objetos similares Especificando algoritmos de propósito general
Ventajas de OOP Promueve el diseño de abstracciones (ocultando el detalle) Promueve la reutilización del código Consolida la especificación y encapsulamiento del interface
Ejemplo Diseño Orientado a Objetos
Similar a las clases de objetos Interfaces comunes Utilización común Reutilización de código – herencia Aplaza la implementación y
algoritmos de decisión.
Diseño Procedural Centrado en los algoritmos –
forza a las implementaciones tempranas y algoritmos de decisión
Expone mayor detalle Dificulta la extensión Dificulta el mantenimiento
Polimorfismo El polimorfismo permite que los algoritmos sean
especificados en términos del tipo de objeto más general (y/o razonable)void processShape(Shape s) { s.setColor(Color.red); // ... Hacer algo s.draw();
}
Circle c = new Circle(x, y, r);
Point2D v1 = new Point2D(x1, y1);Point2D v2 = new Point2D(x2, y2);Point2D v3 = new Point2D(x3, y3);Triangle t = new Triangle(v1, v2, v3);
Point2D c1 = new Point2D(u1, v1);Point2D c2 = new Point2D(u2, v2);Triangle r = new Rectangle(c1, c2);
processShape(c);processShape(t);processShape(r);
Puntos específicos de Java Un programa fuente de java está compuesto de:
Espacios en blanco, comentarios, declaraciones y sentencias.
Los espacios en blanco son una parte importante de cualquier programa en Java No tienen efecto en el significado del código. Enfatiza la lectura y comunicación de estructuras Includes, espacios, tabuladores y líneas nuevas. Tipicamente, tabuladores o identación establece bloques de
objetos. Las nuevas líneas separan las sentancias
No hay reglas estrictas de cómo codificar, pero por lo menos
Adoptar un estilo consistente!
Comentarios Al lado de los espacios en blanco, la parte
más importante de cualquier programa en Java son sus comentarios.
Comentarios en la linea:answer = 42; // valid independent of the input
Bloques de comentarios/*
Metodo: paint.
Objetivo: dibujar el gráfico que será representado en el área denominada lienzo
Parametros: Objeto tipo Graphics
*/
Comentarios especiales Java provee una forma especial de comentarios para generar
documentación automática y páginas web. Este utiliza una herramienta denominada javadoc. Con estos bloques de comentarios especiales diversas variables especiales son reconocidas indicadas por un símbolo de @ al inicio.
Comentarios de documentación:/**
* Epecifica el método interfaz …
*
* @see java.graf
* @author Genaro Mendez
* @version 1.0
*/
public abstract interface ClaseI {
public final static int KARMA = Math.PI + Math.E;
}
Tipos de datos en Java Las variables en Java deben ser objetos, arreglos o uno de los tipos
de datos primitivos. Java tiene 8 tipos de datos primitivos:
(boolean, byte, short, int, long, double, float, char)
Notas: Todos los tipos de datos son con signo (por ello no está la palabra
reservada unsigned) A diferencia de C y C++ que es de 8 bits, las variables char en java son de
16 bits. Los tipos de datos primitivos son pasados por valor
boolean like6837 = true; // boolean tiene valores {true, false}
byte teeth = 32; // bytes van desde -128 to 127
float pi = 3.141592f; // se necesita forzosamente especificar “f”
short classes = 0xbad; // hexadecimal
Arreglos en Java Los arreglos en java caen entre un tipo de dato primitivo y un objeto Propiedades de los arreglos de java:
Pueden contener objetos, tipos primitivos, u otros arreglos Los contenidos son accesibles vía enteros positivos basados encerrados en paréntesis rectangulares La declaración y creación de los arreglos son operaciones distintas Los arreglos son creados con el operador new Los arreglos sin inicializar tienen el valor null Tienen una sola variable de instancia denominada length Como parámetros, de métodos, se pasan como referencia
byte buffer[]; // declaración del arreglo (buffer = null)
buffer = new byte[1024]; // creación del arreglo (contenido inicializado con ceros)
int table[] = new int[10]; // declaración y creación combinados
int sqrs[] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81 }; // con un inicializador
buffer[5] = sqrs[sqrs.length-1]; // referencias a arreglos
int triangle[][] = new int[3][];
triangle[0] = new int[3];
triangle[1] = new int[2];
triangle[2] = new int[1];
Objetos en Java Los objetos son contenedores agregados a los tipos primitivos, arreglos y
otros objetos. Las clases tienen las siguientes propiedades: Todo el código Java está contenido dentro de objetos Los objetos son pasados a los métodos por referencia Definiciones, declaraciones y creación de objetos son distintos. Las instancias de objetos son creados con el operador “new” y tienen un valor de null. Los objetos tienen cuatro formas: la clase, la clase abstracta, la clase interior, y la interfase.
class Circle { // definición del objeto
static double pi = 3.141592f; // variable
double radius; // variable
public Circle(double r) { // constructor
radius = r;
}
public double circumference() { return 2*pi*radius; }
public double area() { return pi*radius*radius; }
}
Circle c; // object declaration
c = new Circle(4.0); // object creation
double a = c.area();
Sentencias en Java Las sentencias es todo lo que resta.
Las sentencias individuales terminan en punto y coma Los bloques son sentencias agrupadas utilizando corchetes La sentencia más simple es la expresión
Una expresión es una cadena de variables y constantes separadas por operadores.
Java permite el siguiente conjunto de operadores dentro de sus sentencias.
. [] () ++ -- ! ~ instanceof
* / % + -
<< >> >>> < > <= >=
== != &
^ |
&& ||
?: = *= /= %= += -= <<= >>= >>>= &= ^= |=
Sentencias de control El segundo tipo de sentencias son las sentencias de control. La
siguiente lista ilustra algunas de las sentencias de control disponibles en Java:
if (BooleanExpression) Statement
if (BooleanExpression) Statement else Statement
while (BooleanExpression) Statement
do Statement while (BooleanExpression)
for (Expression; BooleanExpression; Expression) Statement
break <Label>
continue <Label>
return Expression
Label: Statement
switch (IntegerExpression) {
case IntegerValue:
Statements
default:
Statements
}
Construyendo un applet Iniciaremos escribiendo lo que se denomina un applet de Java. Esta
es una forma de un programa en Java que puede ser incrustado en un documento HTML y accesible desde el web. Un ejemplo de un documento HTML que llama a nuestro applet ejemplo es el siguiente:
<HTML>
<HEAD>
<TITLE>Demo Applet</TITLE>
</HEAD>
<BODY>
<H1>Demo Applet</H1>
<P>Mi clase favorita es Graficación</P>
<HR>
<CENTER>
<APPLET code="Demo.class" width=200 height = 200>
</APPLET>
</CENTER>
<HR>
</BODY>
</HTML>
El ejemplo Java A continuación, el código fuente del archivo demo.java:
import java.applet.*;
import java.awt.*;
public class Demo extends Applet {
int count;
public void init()
{
count = 1;
}
public void paint(Graphics g)
{
g.setColor(Color.red);
for (int y = 15; y < size().height; y += 15) {
int x = (int) (size().width/2 + 30*Math.cos(Math.PI*y/75));
g.drawString("Hola", x, y);
}
showStatus("Paint invovado " + count + " veces" + ((count > 1) ? "s" : ""));
count += 1;
}
}
Graficación en Java
El proceso de rasterización
Revisión de los Displays Rasterizados
El Display se sincroniza con el barrido del CRT Se emplea una memoria especial para la actualización
de la pantalla. Los Pixeles son los elementos discretos desplegados Generalmente, las actualizaciones son visibles.
Sistema de Display Gráfico avanzado
Agrega un segundo frame buffer Se intercambia durante el barrido
vertical Las actualizaciones son invisibles Es más costoso
Implementando un objeto de Rasterización de Memoria
Mantiene una copia de la pantalla (o parte de esta) en memoria.
Depende de una copia rápida Las actualizaciones son casi invisibles Modelo conceptual de un objeto físico.
Un modelo en Java de la Memoria Raster Es posible realizar gráficos empleando el paquete de clases
estándar Java2D y Java3D, pero efectos didácticos y así lograr comprender el funcionamiento del proceso de graficación, iniciaremos el trabajo desde cero. Para empezar crearemos el modelo de memoria del rasterizado.
import java.awt.*;
import java.awt.image.*;
class Raster {
public int width, height;
public int pixel[];
: : :
Descarga el código fuente de la clase Raster (Raster.java)
Los pixeles se almacenan como un arreglo de int denominado pixel[]
•Cada pixel consta de 32 bits•Cuatro bytes uno para cada color primario y un valor Alpha
•Cuando Alpha es 0 es transparente•Cuando Alpha es 255 es opaco
Los métodos de la Clase Raster ///////////////////////// Constructores //////////////////////
/*
* Constructor sin argumentos,
*/
public Raster() {
}
/**
* Este constructor crea un objeto Raster sin inicializar
* de las dimensiones dadas por w (width) y h (height).
*/
public Raster(int w, int h)
{
width = w;
height = h;
pixel = new int[w*h];
}
Los pixeles de la clase Raster Como se mencionó antes los pixeles son almacenados en enteros de 32
bits.
/**
* Este constructor crea un Raster inicializado con
* el contenido de una imagen
*/
public Raster(Image img){
try {
PixelGrabber grabber = new PixelGrabber(img, 0, 0, -1, -1, true);
if (grabber.grabPixels()) {
width = grabber.getWidth();
height = grabber.getHeight();
pixel = (int []) grabber.getPixels();
}
} catch (InterruptedException e) {
}
}
http://www.permadi.com/tutorial/javaGetImagePixels/index.html
Métodos de rasterizado////////////////////////// Metodos //////////////////////////
/* Retorna el número de pixeles */
public final int size( ){
return pixel.length;
}
/* Rellena el objeto Raster con un color solido */
public final void fill(Color c){
int s = size();
int rgb = c.getRGB();
for (int i = 0; i < s; i++)
pixel[i] = rgb;
}
/* Convierte la imagen rasterizada a un objeto Image */
public final Image toImage(Component root){
return root.createImage(new MemoryImageSource(width, height, pixel, 0, width));
}
Más métodos de rasterizado
/* Obtiene un color desde un Raster */
public final Color getColor(int x, int y){
return new Color(pixel[y*width+x]);
}
/* Establece un pixel en un valor dado */
public final boolean setPixel(int pix, int x, int y){
pixel[y*width+x] = pix;
return true;
}
/* Establece un pixel en un color dado */
public final boolean setColor(Color c, int x, int y){
pixel[y*width+x] = c.getRGB();
return true;
}
Ejemplo de uso Rastest.javaimport java.applet.*;
import java.awt.*;
import Raster;
public class Rastest extends Applet {
Raster raster;
public void init() {
String filename = getParameter("image");
raster = new Raster(getImage(getDocumentBase(), filename));
}
public void paint(Graphics g) {
Image output = raster.toImage(this);
g.drawImage(output, 0, 0, this);
}
public void update(Graphics g) {
paint(g);
}
public boolean mouseUp(Event e, int x, int y) {
int s = raster.size();
for (int i = 0; i < s; i++) {
raster.pixel[i] ^= 0x00ffffff;
}
repaint();
return true;
}
}
Para probar el programa Código HTML de la página que incrustará el applet.<HTML>
<HEAD>
<TITLE>Prueba Applet Raster</TITLE>
</HEAD>
<BODY>
<H1>Prueba Applet Raster</H1>
<HR>
<CENTER>
<APPLET code="Rastest.class" width=160 height = 160>
<param name="image" value=“cualquiera.jpg">
</APPLET>
</CENTER>
<HR>
</BODY>
</HTML>
Sprites
Sprites, PixBlt
Sprites Los sprites son objetos gráficos que se
mueven sobre la imagen de fondo denominada escenario o “playfield”.
Un Sprite es un Rasterclass Sprite extends Raster {
int x, y; // posicion actual del sprite en el playfield
int width, height; // tamaño del cuado del sprite
public Sprite(Image image); // inicializa el sprite con una imagen
public void Draw(Raster bgnd); // Dibuja el sprite en el Raster
}
Cosas que hay que considerar
El método Draw() debe manejar la transparencia de pixeles, y debe también manejar todos los casos donde el sprite sale del escenario.
Un Sprite animado es un spriteclass AnimatedSprite extends Sprite {
int frames; // cuadros en un sprite “frames”
// otras variables privadas
public AnimatedSprite(Image images, int frames);
public void addState(int track, int frame, int ticks, int dx, int dy);
public void Draw(Raster bgnd);
public void nextState();
public void setTrack();
}
Un track es una serie de “frames”. El frame se despliega por pequeños periodos de tiempo denominados “ticks”. La posición del sprite es entonces movida (dx,dy) pixeles.
Un “PlayField” es un Sprite y tiene Sprites Animados (Animated Sprites)class Playfield extends Raster {
Raster background; // imagen del playfield
AnimatedSprite sprite[]; // lista de sprites en el playfield
public Playfield(Image bgnd, int numSprites);
public void addSprite(int i, AnimatedSprite s);
public void Draw( );
}
Imágenes en Java (Images) El paquete java.awt.image, es un estándar un poco
confuso. Hay tres interfaces claves para imágenes en Java
ImageProducer genera los pixeles ImageConsumer agrupa los pixeles en imágenes ImageObserver monitorea el progreso de las imágenes.
Métodos getImage() Crea objetos Image imageProducers se alinea a quienes comprenden
el formato del pixel con el que se basa la imagen. Establece un imageConsumer para guardar la
imagen de los datos. Pobremente documentado.
Ejemplo getImage()import java.awt.*;
import java.applet.*;
public class ImageTest extends Applet {
Image jpgimg, gifimg;
public void init() {
jpgimg = getImage(getCodeBase(),
"MITdome.jpg");
gifimg = Toolkit.
getDefaultToolkit().
getImage("cowicon.gif");
}
public void paint(Graphics g) {
g.drawImage(jpgimg, 0, 0, this);
g.drawImage(gifimg, 180, 5, this);
}
}
Otros formatos de imagen? ¿Qué pasa si queremos leer otros formatos de
imagen? .bmp, .ppm, .tif, .tga, .rgb, .ras, .psd
Debemos implementar un imageProducer para leer pixeles, un imageConsumer para crear la imagen y mantener actualizado al imageObserver
¿Donde?¿Cómo?
Por suerte, ya contamos con algo En nuestra clase Rasterpublic final Image toImage(Component root){
return root.createImage(new MemoryImageSource(width, height, pixel, 0, width));
}
El método MemoryImageSource es un imageProducer(root es un imageObserver)
public class ppmDecoder {
Raster imgRaster; // declara un Raster
public ppmDecoder() { }
public Image getppmImage(URL url) {
// abre el url y lee la cabecera de la imagen
.
// crea imgRaster
.
// copia los datos desde el archivo hacia imgRaster
return (imgRaster.toImage());
}
}
PixBlts Los PixBlts son métodos de rasterización para mover y lipiar
sub-bloques de pixeles desde una región de un raster a otra.
Muy utilizado por sistemas de ventanas
•Moviendo ventanas •Realizando scroll de texto•Copiando y limpiando
Parece fácil Aquí un método PixBlt
public void PixBlt(int xs, int ys, int w, int h, int xd, int yd){
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
this.setPixel(raster.getPixel(xs+i, ys+j), xd+i, yd+j);
}
}
}
Algoritmos de dibujo de líneas
Optimización, rapidez y exactitud
Algoritmos de dibujo de líneas Conversión de escaneo
Conversión de escaneo o rasterización Depende de la naturaleza de escaneo de los displays de
rasters Los algoritmos son fundamentales tanto para la
graficación por computadora en 2D y en 3D Transformando lo continuo en lo discreto (sampling) El dibujo de líneas era una tarea fácil para los displays
vectorizados. La mayoría de los algoritmos de dibujo de líneas fueron
desarrollados inicialmente para los plotters de plumas La mayoría de los algoritmos de conversión
desarrollados para plotters se atribuyen a Jack Bresenham
En la búsqueda de la Linea Ideal Lo mejor que se puede hacer es una
aproximación discreta de una linea ideal.
Cualidades importantes de la línea Apariencia continua Grosor y brillantes continua Encendido de los pixeles más cercanos a la línea
ideal Que tan rápido se puede generar la línea.
Linea simple Basado en el algoritmo simple del algebra.
y = mx + b Ver demostración
public void lineaSimple(int x0, int y0, int x1, int y1, Color color) {
int pix = color.getRGB();
int dx = x1 - x0;
int dy = y1 - y0;
raster.setPixel(pix, x0, y0);
if (dx != 0) {
float m = (float) dy / (float) dx;
float b = y0 - m*x0;
dx = (x1 > x0) ? 1 : -1;
while (x0 != x1) {
x0 += dx;
y0 = Math.round(m*x0 + b);
raster.setPixel(pix, x0, y0);
}
}
}
Optimizando lineaSimple ver demostración Problema: lineaSimple() no da resultados satisfactorios para inclinaciones mayores a uno
(>1). Solución: Simetría
public void lineaMejorada(int x0, int y0, int x1, int y1, Color color) {
int pix = color.getRGB();
int dx = x1 - x0; int dy = y1 - y0;
raster.setPixel(pix, x0, y0);
if (Math.abs(dx) > Math.abs(dy)) { // inclinacion < 1
float m = (float) dy / (float) dx; // calcular inclinacion
float b = y0 - m*x0;
dx = (dx < 0) ? -1 : 1;
while (x0 != x1) {
x0 += dx;
raster.setPixel(pix, x0, Math.round(m*x0 + b));
}
} else
if (dy != 0) { // inclinacion >= 1
float m = (float) dx / (float) dy; // Calcular inclinacion
float b = x0 - m*y0;
dy = (dy < 0) ? -1 : 1;
while (y0 != y1) {
y0 += dy;
raster.setPixel(pix, Math.round(m*y0 + b), y0);
}
}
}
Optimizando ciclos internos Se optimizan éstos fragmentos de código donde el algoritmo
pierde la mayor parte de su tiempo. Remover la invocación a métodos que sean innecesarios.
Reemplazamos Math.round(m*x0 + b)
con (int) (m * y0 + b + 0.5)
Utilizar cálculos incrementales.
Considerar la expresión
y = (int) (m * y0 + b + 0.5)
El valor de y es conocido en x0 (por ejemplo es y0 + 0.5) Valores futuros de y pueden ser expresados en términos de sus
valores anteriores con una ecuación de diferencias.
yi+1 = yi + m o yi+1 = yi - m
Algoritmo modificado ver demostración Este método de dibujo de línea se denomina “Digital Differential Analyzer” o DDApublic void lineaDDA(int x0, int y0, int x1, int y1, Color color) {
int pix = color.getRGB();
int dy = y1 - y0; int dx = x1 - x0;
float t = (float) 0.5; // desplazamiento a redondear
raster.setPixel(pix, x0, y0);
if (Math.abs(dx) > Math.abs(dy)) { // inclinacion < 1
float m = (float) dy / (float) dx; // calcular inclinacion
t += y0;
dx = (dx < 0) ? -1 : 1;
m *= dx;
while (x0 != x1) {
x0 += dx; // paso al siguiente valor x
t += m; // agregar la inclinación al valor y
raster.setPixel(pix, x0, (int) t);
}
} else { // inclinación >= 1
float m = (float) dx / (float) dy; // calcular la inclinación
t += x0;
dy = (dy < 0) ? -1 : 1;
m *= dy;
while (y0 != y1) {
y0 += dy; // paso al siguiente valor y
t += m; // agregar inclinación al valor x
raster.setPixel(pix, (int) t, y0);
}
}
}
Comparación de algoritmos Los compiladores modernos implementan estos tipos
de optimizaciones. Dilema: ¿Es mejor mantener un código legible y depender de un
compilador para que realice implícitamente las optimizaciones o codificamos la optimización explícitamente perdiendo algo de legibilidad?
La respuesta no es muy clara. En general, se debe codificar lo más legiblemente posible. Los comentarios pueden resultar muy largos al explicar el código optimizado. Por otra parte rara vez se negocia la portabilidad por la elegancia. De ésta manera, si la aplicación depende de un algoritmo rápido, se prefiere codificar directamente que esperar a que el compilador realice el trabajo, y más aún si se desconoce el resultado debido a las diferentes versiones y plataformas de los compiladores.
Ver demostración de comparación de Algoritmos
Optimizaciones de bajo nivel Las optimizaciones de bajo nivel son problemáticas, debido
a que dependen del detalle específico de la máquina. Sin embargo, un conjunto de reglas generales que son mas
o menos consistentes en todas las máquinas, incluye: La adición y substracción son generalmente más rápidas que la
multiplicación La Multiplicación es generalmente más rápida que la división Utilizar tablas para evaluar funciones discretas es más rápido que
calcularlas. Los cálculos de números enteros son más rápidos que los cálculos
de números de punto flotante. Evitar los cálculos innecesarios probando varios casos especiales. Las pruebas o comparaciones intrínsecas en la mayoría de las
máquinas son “mayor que”, “menor que”, “mayor o igual “, “menor o igual” a cero.
Aplicación de optimizaciones de bajo nivel Observamos que la inclinación siempre es racional
(un radio de dos enteros)m = (y1 – y0) / (x1 – x0)
Observamos también que la parte incremental del algoritmo nunca genera un nuevo y que sea más que una unidad, en relación a la anterior (debido a que la inclinación siempre es menor a uno)
yi+1 = yi + m
Así, si mantenemos solo una parte fraccional de y podremos estar dibujando la línea hasta notar que la fracción ya excede la unidad. Si inicializamos la fracción con 0.5, entonces manejaremos el redondeo correctamente en la rutina DDA
fraccion += m;if (fraccion >= 1) { y = y + 1; fraccion -= 1; }
Mas optimizaciones de bajo nivel “y” ahora es un entero. ¿Podremos representar la fracción como un
entero?
Después de que se dibuja el primer pixel (lo cual sucede fuera del ciclo principal) la fracción correcta es:
fraction = ½ + dy/dx Si escalamos la fracción por 2*dx resulta en la
siguiente expresión:
scaledFraction = dx + 2*dy Y la actualización incremental se convierte en:
scaledFraction = 2*dy Y nuestra prueba debe ser modificada para reflejar la
nueva escala.
if (scaledFraction >= 2*dx)
Mas optimizaciones de bajo nivel (2) Esta prueba se puede hacer contra el valor cero si el valor
inicial de scaledFraction y tiene restado 2*dx de éste. Dando, fuera del ciclo:
OffsetScaledFraction = dx + 2*dy – 2*dx = 2*dy – dx
En el interior del ciclo se convierte en:
OffsetScaledFraction += 2*dy;
if (OffsetScaledFraction >= 0) {
y = y + 1;
fraction -= 2 * dx
} Podemos también duplicar los valores de dy y dx (este puede ser
completado tanto con la suma o con corrimientos fuera del ciclo)
El método resultante se conoce como Algoritmo de Bresenham
public void lineaBresenham(int x0, int y0, int x1, int y1, Color color) {
int pix = color.getRGB();
int dy = y1 - y0; int dx = x1 - x0;
int stepx, stepy;
if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; }
if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }
dy <<= 1; dx <<= 1 // dy es ahora 2*dy y dx es ahora 2*dx
raster.setPixel(pix, x0, y0);
if (dx > dy) {
int fraction = dy - (dx >> 1); // igual que 2*dy - dx
while (x0 != x1) {
if (fraction >= 0) {
y0 += stepy; fraction -= dx; // igual que fraction -= 2*dx
}
x0 += stepx; fraction += dy; // igual que fraction -= 2*dy
raster.setPixel(pix, x0, y0);
}
} else {
int fraction = dx - (dy >> 1);
while (y0 != y1) {
if (fraction >= 0) {
x0 += stepx; fraction -= dy;
}
y0 += stepy;
fraction += dx; raster.setPixel(pix, x0, y0);
}
}
}
ver demostración
Cuestión de Infraestructura Existe todavía una multiplicación oculta dentro del ciclo
interno. /**
* Sets a pixel to a given value
*/
public final boolean setPixel(int pix, int x, int y)
{
pixel[y*width+x] = pix;
return true;
}
La siguiente optimización del algoritmo de Bresenham elimina incluso ésta multiplicación.
Algoritmo Bresenham más rápido public void lineFast(int x0, int y0, int x1, int y1, Color color) {
int pix = color.getRGB();
int dy = y1 - y0; int dx = x1 - x0; int stepx, stepy;
if (dy < 0) { dy = -dy; stepy = -raster.width; } else { stepy = raster.width; }
if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }
dy <<= 1; dx <<= 1;
y0 *= raster.width; y1 *= raster.width; raster.pixel[x0+y0] = pix;
if (dx > dy) {
int fraction = dy - (dx >> 1);
while (x0 != x1) {
if (fraction >= 0) {
y0 += stepy; fraction -= dx;
}
x0 += stepx; fraction += dy;
raster.pixel[x0+y0] = pix;
}
} else {
int fraction = dx - (dy >> 1);
while (y0 != y1) {
if (fraction >= 0) {
x0 += stepx; fraction -= dy;
}
y0 += stepy; fraction += dx;
raster.pixel[x0+y0] = pix;
}
}
}
Mas algoritmos
El algoritmo de “Two Step” (2-pasos) toma el interesante enfoque de tratar el dibujo de una línea como un autómata o máquina de estados finitos.
Si observamos la posible configuración de los siguientes dos pixeles de una línea, es fácil saber que solo existe un conjunto finito de posibilidades.
El algoritmo de “Two step” también explota la simetría del dibujo de líneas dibujando simultáneamente desde los extremos hacia el punto medio.
El código es un poco largo para que pueda se expuesto de forma completa en una lámina, por lo cual éste puede ser visto en la siguiente liga: lineaTwoStep
Con tantos libros haciendo referencia al método Bresenham, podríamos pensar que el desarrollo de algoritmos de dibujo, terminó con el famoso algoritmo de Bresenham. Sin embargo, ha habido un trabajo significativo desde entonces. El siguiente algoritmo denominado “Two Step”, desarrollado por Xiaolin Wu.
Algunos puntos que considerar Puntos extremos en coordenadas no enteras
(ocurre frecuentemente en procesos de dibujo de lineas 3D)
¿Qué sucede si los extremos de la línea están fuera del área de visualización?
¿Cómo se maneja el grosor de las líneas? Optimizaciones para segmentos de líneas
conectadas. Las líneas se muestran en lugares extraños
(considerar aspecto de la imagen)
Dibujo de polígonos
El triángulo
Dibujo de triángulos Los triángulos son quizás la primitiva de
dibujo más importante. Los triángulos son el polígono mínimo. Están
determinados por solo 3 puntos o 3 ejes.
Triángulos son siempre polígonos convexos.
No-ConvexoConvexo
Dibujo de triángulos Los triángulos son matemáticamente
muy simples. La matemática relacionada con los triángulos
involucra solamente ecuaciones de primer grado.
Las formas arbitrarias pueden aproximarse con triángulos. Cualquier figura bidimensional puede
aproximarse con un polígono utilizando aproximación lineal a la superficie. Para mejorar la calidad de ajuste solo se necesita incrementar el número de lados.
Cualquier polígono puede ser fraccionado en triángulos.
Estrategias de dibujo de triángulos Hay dos estrategias comunes para el dibujo de triángulos
rellenos. La primera utiliza un seguimiento alrededor de los ejes y la segunda por ecuaciones de ejes.
Seguimiento de ejes: Ordena los vértices tanto “x” y “y”. Determina si el vértice de en medio, se encuentra a la
derecha o izquierda del polígono. Determina el eje derecho o izquierdo para cada línea de
seguimiento Ventajas y Desventajas
Cargado de casos especiales De difícil exactitud Requiere del cálculo de desplazamientos
fraccionales cuando se interpolan parámetros Generalmente muy rápido.
Método por ecuaciones de ejes Otro enfoque para el dibujo de triángulos
utiliza las ecuaciones de ejes para determinar cuales pixeles se deben rellenar.
Una ecuación de ejes segmenta una región en tres partes, un limite y dos mitades de espacio. El límite está identificado por puntos donde la ecuación del eje es igual a cero. Las dos mitades están distinguidas por las diferencias en los signos de la ecuación de los ejes.
Características Calcular las ecuaciones de ejes desde los vértices. Calcular una limite alrededor de la figura. Hacer un seguimiento de los pixeles dentro del limite
de la figura evaluando las ecuaciones de los ejes. Cuando todos dan un valor positivo se dibuja el pixel.
Implementación Antes de empezar es necesario definir unos
cuantos objetos útiles.public class Vertex2D {
public float x, y; // coordenada oe vertex
public int argb; // color de vertex
public Vertex2D(float xval, float yval, int cval)
{
x = xval;
y = yval;
argb = cval;
}
}
El objeto EdgeEqnclass EdgeEqn {
public final static int FRACBITS = 12;
public int A, B, C;
public int flag;
public EdgeEqn(Vertex2D v0, Vertex2D v1){
double a = v0.y - v1.y;
double b = v1.x - v0.x;
double c = -0.5f*(a*(v0.x + v1.x) + b*(v0.y + v1.y));
A = (int) (a * (1<<FRACBITS));
B = (int) (b * (1<<FRACBITS));
C = (int) (c * (1<<FRACBITS));
flag = 0;
if (A >= 0) flag += 8;
if (B >= 0) flag += 1;
}
public void flip(){
A = -A;
B = -B;
C = -C;
}
public int evaluate(int x, int y){
return (A*x + B*y + C);
}
}