Kirjautuminen

Haku

Tehtävät

Koodit: Java: Suurennuslasi

Kirjoittaja: FooBat

Kirjoitettu: 20.08.2004 – 20.08.2004

Tagit: koodi näytille, vinkki

Näytönsäästäjistä tuttu nopea vääristävä suurennuslasi Javalla toteutettuna. Erikoisuutena suurennuslasin lasi on värjätty. Efektin kokoa voi myös muuttaa ajon aikana + ja - merkeillä (klikkaa kuvaa ensin kerran fokuksen saamiseksi).

Efektin nopeus perustuu taulukkoon lasketuista suurennusvakiosta ja kuvan laskemisesta muistipuskuriin pikseli kerrallaan tulostamisen sijaan. MemoryImageSource on tutustumisen arvoinen luokka, jos aikoo tehdä jotain vastaavaa.

Koodi on varsin optimoitua Java-koodiksi, vaikkakin värjäyksen poistamalla ja efektin koon asettamisella joksikin pyöreäksi vakioksi (kuten 128) saataisiin sitä vielä hiukan nopeammaksi.

Lisäksi ohjelma ei varaa prosessoria turhaan vaan hoitaa piirtämisen suhteellisen fiksusti.

LookingGlass.java

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;

public class LookingGlass extends Canvas implements MouseMotionListener, KeyListener {

  /**
   * Ruudulle tulostettavan kuvan pikselit
   */
  private int[] screen;

  /**
   * Alkuperaisen kuvan pikselit
   */
  private int[] picture;

  /**
   * 256x256 kokoinen taulukko, jossa lasin taittovakiot
   * taulukoituna. Arvot 0-256.
   * 0 = ääretön zoomaus (piirretään polttopisteen pikseli)
   * 128 = 2:1 zoomaus
   * 256 = 1:1 zoomaus
   *
   * Algoritmin kannalta myös seuraavat olisi mahdollisia,
   * mutta renderointilooppiin pitäisi lisätä pari tarkistusta,
   * että piirrettä pikseli ei ole ruudun (ja muistitaulukon)
   * ulkopuolella.
   *
   * < 0 kuvan kääntävä zoomaus
   * > 256 pientävä suurennuslasi
   */
  private int[] lookingGlass;

  // hiiren edelliset koordinaatit
  private int mouseX;
  private int mouseY;

  /**
   * Onko hiiri liikkunut sitten viime piirron.
   */
  public boolean bMouseMoved = false;

  /**
   * Ruudulle piirrettävä renderöity kuva
   */
  private Image screenImage;

  /**
   * Taikakalu, joka saa screenImagen käyttämään
   * screen-taulukkoa muistipuskurina
   */
  private MemoryImageSource mis;

  /**
   * Kuvan koko
   */
  public int w, h;

  /**
   * Suurennuslasin säde efektissä.
   * Voi muuttaa ajon aikana + ja - merkeillä.
   */
  private int radius = 128;


  //lasin väri vastavärinä                 |
  //                                       V
  private static final int redG   = 0xff - 150;
  private static final int greenG = 0xff - 30;
  private static final int blueG  = 0xff - 30;

  public  LookingGlass() {
    //Otetaan kuvan leveys ja korkeus
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    w = toolkit.getScreenSize().width;
    h = toolkit.getScreenSize().height;

    //Otetaan kuvakaappaus
     try {
       Robot robot = new Robot();
       Image image = robot.createScreenCapture(new Rectangle(0, 0, w, h));

       //muutetaan kuva integer taulukoksi
       picture = getPixels(image);
     } catch (AWTException e) {
       System.exit(0);
     }


    lookingGlass = new int[256*256];
    initLookingGlass();

    screen = new int[ w * h ];


    // luodaan yhteys integer taulukon ja piirrettävän kuvan välille
    mis=new MemoryImageSource(w,h,ColorModel.getRGBdefault(),screen,0,w);
    mis.setAnimated(true);
    mis.setFullBufferUpdates(true);
    screenImage = createImage(mis);

    addMouseMotionListener(this);
    addKeyListener(this);

  }

  /**
   * Laskee taulukkoon etäisyyden mukaan
   * kasvavia arvoa väliltä 0-256.
   * Muunkinlaista jakaumaa voi kokeilla,
   * mutta renderöinti odottaa saavansa lukuja
   * tuolta väliltä ja jättää siten turhat tarkistukset pois.
   * Pitää myös huomata, että kaikki vakiot pienentävät tai
   * suurentavat vain polttopisteen (hiiren koordinaattien) suhteen.
   * Useampi polttopiste vaatisi erilliset taulukot x- ja y-koordinaateille
   * ja laskukaavassa pitäisi käyttää polttopisteenä aina kulloinkin
   * piirrettävää pistettä.
   */
  private void initLookingGlass() {

    for (int x = 0; x < 256; x++)
      for (int y = 0; y < 256; y++) {

	int dx = x-128;
	int dy = y-128;
	int D = (int)(2*Math.sqrt(dx*dx + dy*dy));

	if (D > 256)
	  D = 256;

	lookingGlass[(y<<8) + x] =  D;
      }
  }

  /**
   * Hakee kuvan pikselit kuvasta
   * integer taulukkoon.
   */
  public int[] getPixels(Image image) {
    int w, h;
    w = image.getWidth(this);
    h = image.getHeight(this);
    int[] pixels = new int[w*h];

    PixelGrabber pg
      = new PixelGrabber(image,0,0,w,h,pixels,0,w);
    try {
      pg.grabPixels();
    }
    catch (InterruptedException e) {}
    return pixels;
  }

  /**
   * Piirretään ainoastaan jo valmiiksi laskettu kuva
   */
  public void paint(Graphics g) {
    g.drawImage(screenImage,0,0,this);
  }

  public void update(Graphics g) {
    paint(g);
  }


  /**
   * Renderointilooppi. Piirtää yhden kuvan muistipuskuriin.
   */
  private final void render() {
    //kohdekuvan indeksiä laskeva muuttuja
    int offs = 0;

    //kopioidaan alkuperäinen kuva kohteeseen
    System.arraycopy(picture, 0, screen, 0, screen.length);

    int mY = mouseY; // estetään hiiren arvoja muuttumasta renderoinnin aikana
    int mX = mouseX;
    int r = radius;
    bMouseMoved = false;

    //lasketaan suurennuslasin efektiä vain sen säteen kokoisen nelion sisällä
    int minY = mY-r+1;
    if (minY < 0) minY = 0;
    int maxY = mY+r-1;
    if (maxY >= h) maxY = h-1;
    int minX = mX-r+1;
    if (minX < 0) minX = 0;
    int maxX = mX+r-1;
    if (maxX >= w) maxX = w-1;

    int mouseOffs = mY*w +mX;
    int rowStart = minY*w;


    for (int y = minY; y < maxY; y++) {
      offs = rowStart+minX;
      int yDiff = (y-mY); //pisteen etäisyys polttopisteestä
      int lgY  =
	((128 - (yDiff<<7)/r) //skaalataan etäisyys taulukkoon sopivaksi
	 << 8 )     // Y on taulukon rivi (*256)
	+128;       // X valmiiksi puoleenväliin
      for (int x = minX; x < maxX; x++) {
	int xDiff = (mX-x);
	int lgX  = ((xDiff)<<7)/r;

	int zoom = lookingGlass[lgY+lgX]; // haetaan voimakkuus taulukosta

	//skaalataan etäisyydet zoomin mukaan
	int dy = (zoom * yDiff)>>8;  // diff * ( zoom / 256.0 )
	int dx = (zoom * xDiff)>>8;

	//lasketaan lasin värin (vastavärin) vahvuus
	//lasin paksuuden perusteella
	int I = 256 - zoom;
	int ri = (I * redG   )>>8;    // lasin väri * ( I / 256.0)
	int gi = (I * greenG )>>8;
	int bi = (I * blueG  )>>8;

	// haetaan zoomin kohdepikseli polttopisteen läheltä
	int color = picture[mouseOffs+dy*w-dx];


	//vähennetään lasin värin vastaväriä taustan väristä
	int red   = ((color>>16) & 0xff) - ri;
	int green = ((color>>8)  & 0xff) - gi;
	int blue  = ( color      & 0xff) - bi;

	if (red < 0)
	  red = 0;
	if (green < 0)
	  green = 0;
	if (blue < 0)
	  blue = 0;

	//asetetaan kuvan pikseli
	screen[offs++ ] = 0xff000000 | (red << 16) | (green << 8) | blue;
      }
      rowStart += w;
    }
  }

  public void mouseMoved(MouseEvent e) {
    mouseX = e.getX();
    mouseY = e.getY();
    bMouseMoved = true;
  }

  public void keyPressed(KeyEvent e) {
    if (e.getKeyCode() == e.VK_PLUS) {
      radius++;
      bMouseMoved = true;
    }
    if (e.getKeyCode() == e.VK_MINUS) {
      radius--;
      if (radius <= 0)
	radius = 1;
      bMouseMoved = true;
    }

    if (e.getKeyCode() == e.VK_ESCAPE)
      System.exit(0);
  }

  public void keyReleased(KeyEvent e) {}
  public void mouseDragged(MouseEvent e) {}
  public void keyTyped(KeyEvent e) {}


  /**
   * Testaa renderöintiloopin nopeutta
   */
  public void test() {

    mouseX = 500;
    mouseY = 400;
    int n = 100;
    long time = System.currentTimeMillis();
    for (int i = 0; i < n; i++)
      render();
    System.out.println("Render time: "+((System.currentTimeMillis()-time)/(double)n));

  }

  /**
   * Laskee seuraavan kuvan
   * ja ilmoittaa, että uusi kuva
   * olisi tarjolla.
   */
  public void nextFrame() {
    render();
    mis.newPixels(0, 0, w, h);
  }

  public static void main(String[] args) {

    LookingGlass lg = new LookingGlass();
    JFrame frame = new JFrame("Looking Glass");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setSize(lg.w,lg.h);
    frame.getContentPane().add(lg);
    //  lg.test();
    frame.show();

    //Laitetaan threadin prioriteetti minimille
    //Piirtää ihan yhtä nopeasti tälläkin, muttei
    //jumita muita prosesseja.
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

    //Looppi, joka piirtää kuvaa, jos hiirtä on liikuteltu.
    //Reagoi suht nopeasti, muttei varaa prosessoria turhaan
    //(Voi kuunnella taustalla Winamp:iä ;)
    while(frame.isShowing()) {
      if (lg.bMouseMoved)
	lg.nextFrame();
      else {
	try {
	  Thread.sleep(50);
	}catch(Exception e) {return;}
      }
    }
  }
}

Kommentit

Sami [21.08.2004 01:28:16]

#

Kiva :D
Vielä kun lisäät sinne frame.setUndecorated(true); niin saat sen näkymään ihan koko ruudun kokoisena, eikä Windowsin tehtäväpalkki tai ohjelman kehykset tule häiritsemään...

Jotenkin tästä tuli mieleen myös muutama kuukausi sitten väsäämäni hieman samankaltainen koodivinkki https://www.ohjelmointiputka.net/koodivinkit/24327-java-zoomaus

FooBat [21.08.2004 01:56:35]

#

Jätin setUndecorated käskyn tahallaan pois, että ihmiset eivät joutuisi paniikkiin, kun eivät pääse pois ohjelmasta :)

Näin tuon vinkkisi aikaisemmin ja päätin tehdä tämän version suurennuslasista.

Itse asiassa googletin jotain asiaa pari päivää sitten ja törmäsin palloja väriliu'ulla vinkkiisi. Efekti oli ihan nätti, mutta vähän hidas. Tulin lopulta käyttäneeksi pari iltaa sen optimoimisen kanssa ja ajettelin itsekin rekisteröityä tänne jakamaan joitakin vinkkejä. (Voin pistää ne optimoidut versiot siitä efektistä jonnekin, jos haluat)

Sami [21.08.2004 02:21:55]

#

Ihan vapaasti saat käyttää niitä vinkkejä mihin haluat ja tietysti olisi ihan mukavaa nähdä kuinka hyvin sait optimoitua sitä koodia.
Tästä muuten huomaa hyvin myös sen, että optimointi on se aikaavievin osuus. Tein nimittäin sen väriliu'un ja ne pallot pääosin yhden illan aikana ja seuraavana päivänä vielä pientä säätöä mm. niiden väreihin ja kokoihin :)

FooBat [21.08.2004 02:41:16]

#

Tein siitä pari eri versiota. Toinen animoi kuvia mahdollisimman nopeasti ja toinen piirtää kuvan osa kerrallaan interlace tekniikalla. (Poistin myös setterit ja getterit Ball-luokasta :) Lisäsin kommentteja jonkin verran jompaan kumpaan versioon.

//AnimOptBalls.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;

import java.util.Random;

import java.awt.image.*;
import java.awt.Image;

public class AnimOptBalls extends JFrame implements WindowListener, KeyListener {
  //Pallojen määrä
  private static final int BALLS = 300;

  //Pallo-oliotaulukko (sis. tiedon pallon koordinaateista, väristä ja koosta)
  private OptBall[] balls = new OptBall[BALLS];
  private int [] ballIxs = new int[BALLS];
  private boolean[][] ballCrossX;
  private int[] byA;
  private int[][] bxA;

  private MemoryImageSource mis;
  private Image memoryImage;
  private int [] pixels;

  private int[] ballIntensity;

  //Koneen resoluution selvittämistä varten
  private Toolkit toolkit;
  private Random random;

  private int height;
  private int width;


  //Taulukot taustan ylimmän ja alimman rivin väriarvoille
  private int[] redTop;
  private int[] redBot;
  private int[] greenTop;
  private int[] greenBot;
  private int[] blueTop;
  private int[] blueBot;

  public AnimOptBalls() {
    //Laitetaan ikkuna koko ruudun kokoiseksi ja poistetaan 'turha' otsikkorivi
    toolkit = Toolkit.getDefaultToolkit();
    //this.setSize(toolkit.getScreenSize().width, toolkit.getScreenSize().height);
    this.setSize(640, 400);
    this.setUndecorated(true);
    addWindowListener(this);
    addKeyListener(this);
    random = new Random();
    for (int i = 0; i < BALLS; i++) {
      balls[i] = new OptBall();
    }
    width = this.getWidth();
    height = this.getHeight();
    ballIntensity = new int[256*256];
    initIntensity();

    byA = new int[BALLS];
    ballCrossX = new boolean[BALLS][width];
    bxA = new int[BALLS][width];

    pixels =new int[this.getWidth()*this.getHeight()];

    mis=new MemoryImageSource(this.getWidth(),this.getHeight(),ColorModel.getRGBdefault(),pixels,0,this.getWidth());
    mis.setAnimated(true);
    mis.setFullBufferUpdates(true);
    memoryImage=createImage(mis);

    redTop = new int[width];
    redBot = new int[width];
    greenTop = new int[width];
    greenBot = new int[width];
    blueTop = new int[width];
    blueBot = new int[width];

    if (memoryImage == null) {
      System.out.println("mi = null");
      System.exit(0);
    }

  }

  private void initIntensity() {

    for (int x = 0; x < 256; x++)
      for (int y = 0; y < 256; y++) {

	int dx = x-128;
	int dy = y-128;


        int I = 255-(int)(2*Math.sqrt(dx*dx + dy*dy));

	if (I < 0)
	  I = 0;
	ballIntensity[(y<<8) + x] =  I; // värien intensiteetit integer arvona, max 255, min = 0
      }
  }

  public void render() {

 //Taustan väriliu'un väriarvot. Saa muuttaa...
    int r = random.nextInt();

    int R_LEFT_TOP = ((r & 0x1) > 0)? 255 : 0;
    int G_LEFT_TOP = ((r & 0x2) > 0)? 255 : 0;
    int B_LEFT_TOP = ((r & 0x4) > 0)? 255 : 0;

    int R_RIGHT_TOP = ((r & 0x10) > 0)? 255 : 0;
    int G_RIGHT_TOP = ((r & 0x20) > 0)? 255 : 0;
    int B_RIGHT_TOP = ((r & 0x40) > 0)? 255 : 0;

    int R_LEFT_BOT = ((r & 0x100) > 0)? 255 : 0;
    int G_LEFT_BOT = ((r & 0x200) > 0)? 255 : 0;
    int B_LEFT_BOT = ((r & 0x400) > 0)? 255 : 0;

    int R_RIGHT_BOT = ((r & 0x1000) > 0)? 255 : 0;
    int G_RIGHT_BOT = ((r & 0x2000) > 0)? 255 : 0;
    int B_RIGHT_BOT = ((r & 0x4000) > 0)? 255 : 0;


    //Vähennetään huomattavasti tarvittavien laskutoimitusten määrää laskemalla taustan
    //ylimmän ja alimman rivin väriarvot valmiiksi (niitä käytetään koko taustan laskemisessa...

    for (int x = 0; x < width; x++) {
      int mul1 = ((width - x)<<8) / width; //suoritetaan jakolasku vain kerran
      int mul2 = 256-mul1;

      redTop[x]   = ( mul1 * R_LEFT_TOP + mul2 * R_RIGHT_TOP ) >> 8;
      redBot[x]   = ( mul1 * R_LEFT_BOT + mul2 * R_RIGHT_BOT ) >> 8;
      greenTop[x] = ( mul1 * G_LEFT_TOP + mul2 * G_RIGHT_TOP ) >> 8;
      greenBot[x] = ( mul1 * G_LEFT_BOT + mul2 * G_RIGHT_BOT ) >> 8;
      blueTop[x]  = ( mul1 * B_LEFT_TOP + mul2 * B_RIGHT_TOP ) >> 8;
      blueBot[x]  = ( mul1 * B_LEFT_BOT + mul2 * B_RIGHT_BOT ) >> 8;

    }

    //Arvotaan päälle tuleville palloille koordinaatit, väri ja koko

    for (int i = 0; i < BALLS; i++) {
      //balls[i] = new Ball();
      balls[i].x = random.nextInt( width );
      balls[i].y = random.nextInt( height );
      balls[i].radius = 20 + random.nextInt( 50 );
      int color = random.nextInt();
      balls[i].red = (color & 0x00ff0000) >> 16;
      balls[i].green = (color & 0x0000ff00) >> 8;
      balls[i].blue = (color & 0x000000ff);

      // lasketaan taulukkoon, millä x:n arvolla pallo on kohdalla
      for (int x = 0; x < width; x++) {
	ballCrossX[i][x] = false;
      }
      int xmax = balls[i].x+balls[i].radius;
      int xmin = balls[i].x-balls[i].radius;
      if (xmax > width)
	xmax = width;
      if(xmin < 0)
	xmin = 0;
      for (int x = xmin; x < xmax; x++) {
	bxA[i][x] = ((x - balls[i].x + balls[i].radius)<<7)/balls[i].radius;
	ballCrossX[i][x] =  ((bxA[i][x] & 0xffffff00) == 0);
	bxA[i][x] <<=8; //kerrotaan valmiiksi intensiteetti taulukon leveydellä (256)
      }
    }


    int offs = 0;
    int colMul1;
    int colMul2;
    //Käydään jokainen kuvan piste läpi ja lasketaan sille väriarvo
    for (int y = 0; y < height; y++) {


      int ballsInRow = 0;
      for (int i = 0; i < BALLS; i++) {
	byA[i] = ((y - balls[i].y + balls[i].radius)<<7)/balls[i].radius;
	if  ((byA[i] & 0xffffff00) == 0)
	  ballIxs[ballsInRow++] = i;
      }

      colMul1 = ((height - y)<<8) / height;
      colMul2 = 256 - colMul1;

      for (int x = 0; x < width; x ++) {
	//Oletuksena pisteen väri on musta
	int red = 0, green = 0, blue = 0;

	//Lasketaan mitkä kaikki pallot ovat riittävän lähellä pistettä

	for (int i = 0; i < ballsInRow; i++) {
	  int iBall = ballIxs[i];
	  if (ballCrossX[iBall][x]) {
	    int I = ballIntensity[bxA[iBall][x]+byA[iBall]];
	    int re = (I*balls[iBall].red)  >>4; //estetään overflow neliöitäessä
	    int gr = (I*balls[iBall].green)  >>4;
	    int bl = (I*balls[iBall].blue)  >>4;
	    //red += re;
	    //green += gr;
	    //blue += bl;
	    red += (re*re);
	    green += (gr*gr);
	    blue += (bl*bl);
	  }
	}

	//Jos yksikään pallo ei ollut riittävän lähellä pistettä, niin piirretään taustaa

	if (red == 0 && green == 0 && blue == 0) {

	  red = (colMul1 * redTop[x] + colMul2 * redBot[x]) >> 8;
	  green = (colMul1 * greenTop[x] + colMul2 * greenBot[x]) >> 8;
	  blue = (colMul1 * blueTop[x] + colMul2 * blueBot[x]) >> 8;
	}
	else {

	  red = (int)Math.sqrt(red);
	  green = (int)Math.sqrt(green);
	  blue = (int)Math.sqrt(blue);
	  red >>=4; //skaalataan intensiteetillä kertominen oikeaksi väriksi
	  green >>=4;
	  blue >>=4;

	  if (red > 255)   red = 255;
	  if (green > 255) green = 255;
	  if (blue > 255)  blue = 255;

	}


	pixels[offs++] = 0xff000000 | (red << 16) | (green << 8) | blue;

      }
    }
  }

  public void nextFrame() {
     mis.newPixels(0, 0, width, height);
  }

  public void paint(Graphics g) {

    g.drawImage(memoryImage, 0,0,this);
  }

  //-------------------- MAIN ------------

  public static void main(String[] args) {
    AnimOptBalls liuku = new AnimOptBalls();


    int n = 100;
    long time = System.currentTimeMillis();
    for (int i = 0; i < n; i++)
      liuku.render();
    System.out.println("Render time: "+((System.currentTimeMillis()-time)/(double)n));

    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

    liuku.setVisible(true);

    while(liuku.isShowing()) {
      liuku.render();
      liuku.nextFrame();
      try {
	Thread.sleep(1);
      }catch(Exception e) {return;}
    }
  }

  public void windowActivated(WindowEvent arg0) {
  }

    public void windowClosed(WindowEvent arg0) {
    }

    public void windowClosing(WindowEvent arg0) {
        System.exit(0);
    }

    public void windowDeactivated(WindowEvent arg0) {
    }

    public void windowDeiconified(WindowEvent arg0) {
    }

    public void windowIconified(WindowEvent arg0) {
    }

    public void windowOpened(WindowEvent arg0) {
    }

    //Näppäinkomennot: Väli tai esc lopettaa, Enter piirtää uuden kuvan
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SPACE
            || e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            System.exit(0);
        }

        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
	  //render();
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void keyTyped(KeyEvent e) {
    }
}

// Ball.java
public class OptBall {
    public int x;
    public int y;
    public int red;
    public int green;
    public int blue;
    public int radius;

    public OptBall() {
    }

    public OptBall(int x,int  y,int  red,int  green,int  blue,int  radius) {
        this.x = x;
        this.y = y;
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.radius = radius;
    }
}

//OptBalls.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;

import java.util.Random;

import java.awt.image.*;
import java.awt.Image;

public class OptBalls extends JFrame implements WindowListener, KeyListener {
  //Pallojen määrä
  private static final int BALLS = 300;

  //Pallo-oliotaulukko (sis. tiedon pallon koordinaateista, väristä ja koosta)
  private OptBall[] balls = new OptBall[BALLS];

  //taulokko johon sijoitetaan pallojen indeksit, jotka x-koordinaatti leikkaa
  private int [] ballIxs = new int[BALLS];

  //kullekin kuvan riville laskettu, mitä palloja se rivi leikkaa.
  private boolean[][] ballCrossY;

  //intensiteetti taulukkoon skaalattujen x ja y-koordinaatteja laskettuna
  private int[] bxA;
  private int[][] byA;

  //256x256 taulukko, jossa valointensiteetit valmiiksi laskettuna
  private int[] ballIntensity;

  //kuinka monta kertaa käydään kuva läpi ennen kuin se on piirretty
  private static final int passes = 7;

  //Mistä pikseleistä lähdetään liikkeelle ja kuinka paljon edetään.
  //Tässä on sama tapa, jolla esim. PNG toteuttaa interlace ominaisuutensa
  private static final int[] xstart = {0,0,4,0,2,0,1};
  private static final int[] xadd   = {8,8,8,4,4,2,2};
  private static final int[] ystart = {0,4,0,2,0,1,0};
  private static final int[] yadd   = {8,8,4,4,2,2,1};

  //Koneen resoluution selvittämistä varten
  private Toolkit toolkit;


  private Random random;

  private int height;
  private int width;


  //Taulukot taustan ylimmän ja alimman rivin väriarvoille
  private int[] redTop;
  private int[] redBot;
  private int[] greenTop;
  private int[] greenBot;
  private int[] blueTop;
  private int[] blueBot;


  public OptBalls() {
    //Laitetaan ikkuna koko ruudun kokoiseksi ja poistetaan 'turha' otsikkorivi
    toolkit = Toolkit.getDefaultToolkit();
    //  this.setSize(toolkit.getScreenSize().width, toolkit.getScreenSize().height);
    this.setSize(640, 400);
    this.setUndecorated(true);
    addWindowListener(this);
    addKeyListener(this);
    random = new Random();
    for (int i = 0; i < BALLS; i++) {
      balls[i] = new OptBall();
    }
    width = this.getWidth();
    height = this.getHeight();
    ballIntensity = new int[256*256];
    initIntensity();

    bxA = new int[BALLS];
    ballCrossY = new boolean[BALLS][height];
    byA = new int[BALLS][height];

    redTop = new int[width];
    redBot = new int[width];
    greenTop = new int[width];
    greenBot = new int[width];
    blueTop = new int[width];
    blueBot = new int[width];
  }

  /**
   * Lasketaan etäisyyden mukaan pienenevä intensiteetti
   * 256x256 taulukkoon. Pallojen koordinaatit skaalataan
   * tähän sopivaksi.
   */
  private void initIntensity() {

    for (int x = 0; x < 256; x++)
      for (int y = 0; y < 256; y++) {

	int dx = x-128;
	int dy = y-128;


        int I = 255-(int)(2*Math.sqrt(dx*dx + dy*dy));

	if (I < 0)
	  I = 0;
	ballIntensity[(y<<8) + x] =  I; // värien intensiteetit integer arvona, max 255, min = 0
      }
  }

  public void render(Graphics g) {

    //Taustan väriliu'un väriarvot. Saa muuttaa...

    //arvotaan kaikki värit yhdellä kertaa :)
    int r = random.nextInt();

    int R_LEFT_TOP = ((r & 0x1) > 0)? 255 : 0;
    int G_LEFT_TOP = ((r & 0x2) > 0)? 255 : 0;
    int B_LEFT_TOP = ((r & 0x4) > 0)? 255 : 0;

    int R_RIGHT_TOP = ((r & 0x10) > 0)? 255 : 0;
    int G_RIGHT_TOP = ((r & 0x20) > 0)? 255 : 0;
    int B_RIGHT_TOP = ((r & 0x40) > 0)? 255 : 0;

    int R_LEFT_BOT = ((r & 0x100) > 0)? 255 : 0;
    int G_LEFT_BOT = ((r & 0x200) > 0)? 255 : 0;
    int B_LEFT_BOT = ((r & 0x400) > 0)? 255 : 0;

    int R_RIGHT_BOT = ((r & 0x1000) > 0)? 255 : 0;
    int G_RIGHT_BOT = ((r & 0x2000) > 0)? 255 : 0;
    int B_RIGHT_BOT = ((r & 0x4000) > 0)? 255 : 0;


    //Vähennetään huomattavasti tarvittavien laskutoimitusten määrää laskemalla taustan
    //ylimmän ja alimman rivin väriarvot valmiiksi (niitä käytetään koko taustan laskemisessa...
    for (int x = 0; x < width; x++) {
      redTop[x] = ((width - x) * R_LEFT_TOP + x * R_RIGHT_TOP) / width;
      redBot[x] = ((width - x) * R_LEFT_BOT + x * R_RIGHT_BOT) / width;
      greenTop[x] = ((width - x) * G_LEFT_TOP + x * G_RIGHT_TOP) / width;
      greenBot[x] = ((width - x) * G_LEFT_BOT + x * G_RIGHT_BOT) / width;
      blueTop[x] = ((width - x) * B_LEFT_TOP + x * B_RIGHT_TOP) / width;
      blueBot[x] = ((width - x) * B_LEFT_BOT + x * B_RIGHT_BOT) / width;
    }

    //Arvotaan päälle tuleville palloille koordinaatit, väri ja koko
    for (int i = 0; i < BALLS; i++) {
      //balls[i] = new Ball();
      balls[i].x = random.nextInt( width );
      balls[i].y = random.nextInt( height );
      balls[i].radius = 20 + random.nextInt( 50 );
      int color = random.nextInt();
      balls[i].red = (color & 0x00ff0000) >> 16;
      balls[i].green = (color & 0x0000ff00) >> 8;
      balls[i].blue = (color & 0x000000ff);

      // lasketaan taulukkoon, millä y:n arvolla pallo on kohdalla
      for (int y = 0; y < height; y++) {
	byA[i][y] = ((y - balls[i].y + balls[i].radius)<<7)/balls[i].radius;
	ballCrossY[i][y] =  ((byA[i][y] & 0xffffff00) == 0);
	byA[i][y] <<=8; //kerrotaan valmiiksi intensiteetti taulukon leveydellä (256)
      }
    }

    //Käydään jokainen kuvan piste läpi ja lasketaan sille väriarvo

    //
    for (int iPass = 0; iPass < passes; iPass++) {
      for (int x = xstart[iPass]; x < width; x += xadd[iPass]) {

	//tarkastetaan mitä palloja x-koordinaatti leikkaa
      	int ballsInCol = 0;
	for (int i = 0; i < BALLS; i++) {

	  bxA[i] = ((x - balls[i].x + balls[i].radius)<<7)/balls[i].radius;
	  if  ((bxA[i] & 0xffffff00) == 0) //bxA[i] == (0-255)
	    ballIxs[ballsInCol++] = i; //taulukoidaan mahdolliset pallot
	}

	for (int y = ystart[iPass]; y < height; y+=yadd[iPass]) {
	//Oletuksena pisteen väri on musta
	  int red = 0, green = 0, blue = 0;

	  //Tarkastetaan vain ne pallot, jotka x-koordinaatti leikkaa.
	  for (int i = 0; i < ballsInCol; i++) {
	    int iBall = ballIxs[i];
	  if (ballCrossY[iBall][y]) {
	    int I = ballIntensity[byA[iBall][y]+bxA[iBall]];
	    int re = (I*balls[iBall].red)  >>4; //estetään overflow neliöitäessä
	    int gr = (I*balls[iBall].green)  >>4;
	    int bl = (I*balls[iBall].blue)  >>4;
	    red += (re*re);
	    green += (gr*gr);
	    blue += (bl*bl);
	  }
	  }
	  //Jos yksikään pallo ei ollut riittävän lähellä pistettä, niin piirretään taustaa
	  if (red == 0 && green == 0 && blue == 0) {
	    red = ((height - y) * redTop[x] + y * redBot[x]) / height;
	    green = ((height - y) * greenTop[x] + y * greenBot[x]) / height;
	    blue = ((height - y) * blueTop[x] + y * blueBot[x]) / height;

	  }
	  else {
	    red = (int)Math.sqrt(red);
	    green = (int)Math.sqrt(green);
	    blue = (int)Math.sqrt(blue);
	    red >>=4; //skaalataan intensiteetillä kertominen oikeaksi väriksi
	    green >>=4;
	    blue >>=4;
	    if (red > 255)   red = 255;
	    if (green > 255) green = 255;
	    if (blue > 255)  blue = 255;

	  }
	  g.setColor(new Color(red, green, blue));
	  //kuva nopeasti esiin
	  g.fillRect(x, y, xadd[iPass], yadd[iPass]);

	  //ihan kiva efekti
	  //g.fillRect(x, y, 1, 1);
	}
      }
    }
  }

  public void paint(Graphics g) {
    render(g);
  }

  public static void main(String[] args) {
    OptBalls liuku = new OptBalls();
    Thread me = Thread.currentThread( );
    me.setPriority(Thread.MIN_PRIORITY);

    liuku.setVisible(true);
  }

  public void windowActivated(WindowEvent arg0) {
  }

    public void windowClosed(WindowEvent arg0) {
    }

    public void windowClosing(WindowEvent arg0) {
        System.exit(0);
    }

    public void windowDeactivated(WindowEvent arg0) {
    }

    public void windowDeiconified(WindowEvent arg0) {
    }

    public void windowIconified(WindowEvent arg0) {
    }

    public void windowOpened(WindowEvent arg0) {
    }

    //Näppäinkomennot: Väli tai esc lopettaa, Enter piirtää uuden kuvan
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SPACE
            || e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            System.exit(0);
        }

        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
	  repaint();
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void keyTyped(KeyEvent e) {
    }
}

sooda [21.08.2004 09:41:07]

#

Binääri olis plussaa ;)

Meitsi [21.08.2004 09:42:11]

#

Tota oliskohan käännettynä....

Sami [21.08.2004 12:28:02]

#

LookingGlass löytyy täältä: http://www.paivola.fi/~sami/java/LookingGlass.zip
ja OptBalls:in luokat täältä: http://www.paivola.fi/~sami/java/OptBalls.zip

ohjelmien pääluokat ovat LookingGlass.class, OptBalls.class ja AnimOptBalls.class

T.M. [21.08.2004 19:57:34]

#

Voisko joku tehdä tosta jonkun sivun missä se java-apletti on?
Tylsää katsella pelkkää koodia.

Sami [21.08.2004 20:05:05]

#

Ei voi, nimittäin nuo eivät ole java-appletteja, vaan java sovelluksia, joita ei voi laittaa nettisivuille.
Lataat vaan nuo valmiiksi käännetyt luokat ja ajat ne esim. menemällä komentoriville ja kirjoittamalla: java [luokannimi]

FooBat [21.08.2004 20:26:53]

#

Kyllähän noi suhteellisen helposti voisi muuttaa apleteiksi, mutta mielummin ohjaan ihmiset Sun:in sivuille hakemaan JRE:n tai mielummin SDK:n. Molemmat ovat täysin ilmaisia.

JRE: (Java Runtime Environment) Javan suorittamiseen tarvittava virtuaalikone ja sälät päälle.
http://java.com/en/download/manual.jsp

SDK: (Software Development Kit) Sisältää JRE:n ja java-luokkien kääntämiseen tarvittavat työkalut.
http://java.sun.com/j2se/1.4.2/download.html
(Download J2SE SDK)

tkok [07.06.2011 11:50:04]

#

Kivasti pyörii +60fps :) ja tyylikäs efekti

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta