M.2 Oscillation Figures

2016/09/15

本章ではリサジュー図形による表現パターンを解説.

リサジュー図形とは

1855年にフランスの科学者J.A.Lissajousが考えついたとされている.オシロスコープの測定方法の一つとして有名.二次元の場合だと2つの波の振幅,位相,周波数の違いを視覚的に確認することができる.

Alt text

リサジュー図形で音楽を可視化

X軸とY軸それぞれにステレオのサウンドを波として表現し,全体として音楽を動的なリサジュー図形として可視化している. http://cdm.link/2015/06/watch-oscilloscope-draw-3d-make-amazing-music-time/

Jerobeam Fenderson .net http://www.jerobeamfenderson.net/post/101351329308/how-it-works

業務で使える!?:「何かをMIT風?に見せる」

M_2_6_01_TOOL.pdeのドローイングと同様のテクニックで任意の画像を表現できる.点と点のつなぎ方,線の濃さなどはパラメータを変えれば容易に様々なバリエーションが試せる.まさにコードの持つ「パラメータ化」という技法が威力を発揮する.

Alt text

業務で使える!?:「画像をレトロゲーム風にする」

M_2_6_01_TOOL.pdeのドローイングにおいて,周辺の点同士のみをつなぐと画像をぶつけて壊れた初代ゲームボーイ風に変換できる.

Alt text ↑ 北斎の元画像

Alt text ↑ レトロゲーム風

以下,手を加えたドローイングツール:

// Based on following code:
//
// M_2_6_01_TOOL.pde
// GUI.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * drawing tool that uses the special drawing method of connecting 
 * all points with every other.
 *
 * MOUSE
 * left click          : set points
 * shift + left click  : remove points
 * right click + drag  : drag canvas
 *
 * KEYS
 * backspace           : delete last point
 * m                   : menu open/close
 * s                   : save png
 * p                   : save pdf
 * i                   : import points from a text file
 * e                   : export points to a text file
 */


// ------ imports ------

import processing.pdf.*;
import java.awt.event.*;
import java.util.Calendar;

// ------ initial parameters and declarations ------

String backgroundImagePath;
PImage backgroundImage;
float imageAlpha = 30;
float eraserRadius = 20;
// minimum distance to previously set point
float minDistance = 10;    

float zoom = 1;
boolean drawing = false;
boolean shiftDown = false;

int pointCount = 0;
ArrayList pointList = new ArrayList();

boolean invertBackground = false;
float lineWeight = 1;
float lineAlpha = 50;

boolean connectAllPoints = true;
float connectionRadius = 150;
int i1 = 0;
float minHueValue = 0;
float maxHueValue = 100;
float saturationValue = 0;
float brightnessValue = 0;
boolean invertHue = false;


// ------ mouse interaction ------

boolean dragging = false;
float offsetX = 0, offsetY = 0, clickX = 0, clickY = 0, clickOffsetX = 0, clickOffsetY = 0;


// ------ ControlP5 ------

import controlP5.*;
ControlP5 controlP5;
boolean GUI = false;
boolean guiEvent = false;
Slider[] sliders;
Range[] ranges;
Toggle[] toggles;
Bang[] bangs;


// ------ image output ------

boolean saveOneFrame = false;
boolean savePDF = false;

// ------ image input ------
PImage img;

void setup() {
  size(800, 800);

  // make window resizable
  // Attention: with Processing 3.0 windows resizing behaves differently than before.
  // You might uncomment the folling line, but the screen will not be updated
  // automativally, when resizing the window.
  //surface.setResizable(true);

  smooth();

  setupGUI();

  background(255);

  guiEvent = false;
}


void draw() {
  if (savePDF) {
    beginRecord(PDF, timestamp()+".pdf");
  }

  colorMode(RGB, 255, 255, 255, 100);

  pushMatrix();
  translate(width/2, height/2);
  scale(zoom);
  translate(-width/2 + offsetX, -height/2 + offsetY);


  if (guiEvent) {
    drawing = false;
  }

  color bgColor = color(255);
  color eraserColor = color(0);
  if (invertBackground) {
    bgColor = color(0);
    eraserColor = color(255);
  } 

  if (guiEvent || saveOneFrame || savePDF || i1 == 0) {
    guiEvent = false;
    i1 = 0;
  }

  // canvas dragging
  if (dragging) {
    offsetX = clickOffsetX + (mouseX - clickX) / zoom;
    offsetY = clickOffsetY + (mouseY - clickY) / zoom;
    i1 = 0;
  }


  // set or delete points
  if (drawing) {
    if (keyPressed && keyCode == SHIFT) {
      // shift pressed -> delete points

      for (int i=pointList.size()-1; i >= 0; i--) {
        PVector p = (PVector) pointList.get(i);
        float x = (mouseX-width/2) / zoom - offsetX + width/2;
        float y = (mouseY-height/2) / zoom -offsetY + height/2;

        if (dist(p.x, p.y, x, y) <= (eraserRadius/zoom)) {
          pointList.remove(i);
        }
      }
      pointCount = pointList.size();

      i1 = 0;
    } 
    else {
      // set points

      float x = (mouseX-width/2) / zoom - offsetX + width/2;
      float y = (mouseY-height/2) / zoom - offsetY + height/2;

      if (pointCount > 0) {
        PVector p = (PVector) pointList.get(pointCount-1);
        if (dist(x, y, p.x, p.y) > (minDistance)) {
          pointList.add(new PVector(x, y));
        }
      } 
      else {
        pointList.add(new PVector(x, y));
      }
      pointCount = pointList.size();
    }
  }


  // ------ draw everything ------
  strokeWeight(lineWeight);
  stroke(0, lineAlpha);
  strokeCap(ROUND);
  noFill();
  tint(255, imageAlpha);

  if (!connectAllPoints || shiftDown || dragging) {
    background(bgColor);
    if (backgroundImage != null && !saveOneFrame && !savePDF) image(backgroundImage, 0, 0);

    // simple drawing method
    colorMode(HSB, 360, 100, 100, 100);

    for (int i=1; i<pointCount; i++) {
      PVector p1 = (PVector) pointList.get(i-1);
      PVector p2 = (PVector) pointList.get(i);
      drawLine(p1, p2);
      i1++;
    }
  } 
  else {
    // drawing method where all points are connected with each other
    // alpha depends on distance of the points  

    // clear background if drawing of the lines will start from the beginning
    if (i1 == 0) {
      background(bgColor);
      if (backgroundImage != null && !saveOneFrame && !savePDF) image(backgroundImage, 0, 0);
    }

    // draw lines not all at once, just the next 100 milliseconds to keep performance
    int drawEndTime = millis() + 100;
    if (saveOneFrame || savePDF) {
      drawEndTime = Integer.MAX_VALUE;
    }

    colorMode(HSB, 360, 100, 100, 100);
    while (i1 < pointCount && millis () < drawEndTime) {
      for (int i2 = 0; i2 < i1; i2++) {
        PVector p1 = (PVector) pointList.get(i1);
        PVector p2 = (PVector) pointList.get(i2);
        drawLine(p1, p2);
      }
      i1++;

      if (savePDF) {
        println("saving to pdf – step " + i1 + "/" + pointCount);
      }
    }
  }

  if (shiftDown || dragging) {
    stroke(0);
    for (int i=0; i<pointCount; i++) {
      PVector p = (PVector) pointList.get(i);
      point(p.x, p.y);
    }
  }

  popMatrix();

  // draw eraser
  if (keyPressed && keyCode == SHIFT) {
    strokeWeight(1);
    stroke(eraserColor);
    noFill();
    ellipse(mouseX, mouseY, eraserRadius*2, eraserRadius*2);
  }


  // ------ image output and gui ------
  if (savePDF) {
    savePDF = false;
    println("saving to pdf – finishing");
    endRecord();
    println("saving to pdf – done");
    i1 = 0;
  }

  if (saveOneFrame) {
    saveFrame(timestamp()+".png");
  }


  // draw gui
  drawGUI();

  // image output
  if (saveOneFrame) {
    if (controlP5.getGroup("menu").isOpen()) {
      saveFrame(timestamp()+"_menu.png");
    }
    saveOneFrame = false;
  }
}

void drawLine(PVector p1, PVector p2) {
  float d, a, h;
  d = PVector.dist(p1, p2);
  a = pow(1/(d/connectionRadius+1), 6);

  if (d <= connectionRadius) {
    if (!invertHue) {
      h = map(a, 0, 1, minHueValue, maxHueValue) % 360;
    } 
    else {
      h = map(1-a, 0, 1, minHueValue, maxHueValue) % 360;
    }
    stroke(h, saturationValue, brightnessValue, a*lineAlpha + (i1%2 * 2));
    line(p1.x, p1.y, p2.x, p2.y);
  }
}


void reset() {
  pointList.clear();
  pointCount = 0;
  i1 = 0;
  offsetX = 0;
  offsetY = 0;

  // reset controllers
  Range r;
  Toggle t;
  controlP5.getController("imageAlpha").setValue(30.0);
  controlP5.getController("eraserRadius").setValue(20.0);
  controlP5.getController("zoom").setValue(1.0);

  if (invertBackground == true) {
    t = (Toggle) controlP5.getController("invertBackground");
    t.setState(false);
  }  
  controlP5.getController("lineWeight").setValue(1.0);
  controlP5.getController("lineAlpha").setValue(50.0);

  r = (Range) controlP5.getController("hueRange");
  r.setLowValue(0);
  r.setHighValue(100);
  controlP5.getController("saturationValue").setValue(0.0);
  controlP5.getController("brightnessValue").setValue(0.0);
  if (invertHue == true) {
    t = (Toggle) controlP5.getController("invertHue");
    t.setState(false);
  }

  controlP5.getController("connectionRadius").setValue(150.0);

  if (connectAllPoints == false) {
    t = (Toggle) controlP5.getController("connectAllPoints");
    t.setState(true);
  }
}


void selectFile() {
  guiEvent = true;

  // opens file chooser
  selectInput("Select an image file", "fileSelected");
}

void fileSelected(File selection) {
  if (selection != null) {
    backgroundImagePath = selection.getAbsolutePath();
    println(backgroundImagePath);
    backgroundImage = loadImage(backgroundImagePath);
  }
}


void selectFileLoadPoints() {
  guiEvent = true;

  // opens file chooser
  selectInput("Select Text File with Point Information", "loadPointPathSelected");
}

void loadPointPathSelected(File selection) {
  if (selection != null) {
    String loadPointsPath = selection.getAbsolutePath(); 
    String[] pointStrings = loadStrings(loadPointsPath);
    pointCount = 0;
    pointList.clear();
    for (int i=0; i<pointStrings.length; i++) {
      String[] pt = pointStrings[i].split(" ");
      if (pt.length == 2) {
        pointList.add(new PVector(float(pt[0]), float(pt[1]), 1));
        pointCount++;
      } 
      else if (pt.length == 3) {
        pointList.add(new PVector(float(pt[0]), float(pt[1]), float(pt[2])));
        pointCount++;
      }
    }
    i1 = 0;
  }
}



void selectFileSavePoints() {
  guiEvent = true;

  // opens file chooser
  selectOutput("Save Points as Text File", "savePointPathSelected");
}


void savePointPathSelected(File selection) {
  if (selection != null) {
    String savePointsPath = selection.getAbsolutePath(); 
    if (!savePointsPath.endsWith(".txt")) savePointsPath += ".txt";
    String[] pointStrings = new String[pointCount];
    for (int i=0; i<pointCount; i++) {
      PVector p = (PVector) pointList.get(i);
      if (p.z > 0) {
        pointStrings[i] = p.x + " " + p.y + " " + p.z;
      } 
      else {
        pointStrings[i] = p.x + " " + p.y + " " + 1;
      }
    }
    saveStrings(savePointsPath, pointStrings);
  }
}


void keyPressed() {

  if (key=='m' || key=='M') {
    GUI = controlP5.getGroup("menu").isOpen();
    GUI = !GUI;
    guiEvent = true;
  }
  if (GUI) controlP5.getGroup("menu").open();
  else controlP5.getGroup("menu").close();

  if (key=='s' || key=='S') {
    saveOneFrame = true;
  }
  if (key=='p' || key=='P') {
    savePDF = true; 
    saveOneFrame = true; 
    println("saving to pdf - starting");
  }
  if (keyCode==BACKSPACE) {
    pointCount -= 1;
    pointCount = max(pointCount, 0);
    pointList.remove(pointCount);
    i1 = 0;
  }
  if (keyCode==SHIFT) {
    shiftDown = true;
  }

  if (key=='e' || key=='E') {
    selectFileSavePoints();
  }
  if (key=='i' || key=='I') {
    selectFileLoadPoints();
  }
  if (key=='l' || key=='L') {
    // Added image loading mode
    img = loadImage("hokusai.jpg");
    for (int gridX = 0; gridX < img.width; gridX++) {
      for (int gridY = 0; gridY < img.height; gridY++) {

        // grid position + tile size
        float tileWidth = width / (float)img.width;
        float tileHeight = height / (float)img.height;
        float posX = tileWidth*gridX;
        float posY = tileHeight*gridY;
        // get current color
        color c = img.pixels[gridY*img.width+gridX];
        // greyscale conversion
        int greyscale =round(red(c)*0.222+green(c)*0.707+blue(c)*0.071);    
        if (greyscale < 60) {
          println(greyscale);
          pointList.add(new PVector(posX, posY));
        }
      }
    }
  }
}

void keyReleased() {
  if (shiftDown) {
    shiftDown = false;
    i1 = 0;
  }
}


void mousePressed() {
  if (mouseButton==LEFT && !guiEvent) {
    drawing = true;
  }

  if (mouseButton==RIGHT) {
    dragging = true;
    clickX = mouseX;
    clickY = mouseY;
    clickOffsetX = offsetX;
    clickOffsetY = offsetY;
  }
}


void mouseReleased() {
  guiEvent = false;
  drawing = false;

  if (dragging) {
    dragging = false;
    i1 = 0;
  }
}

String timestamp() {
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", Calendar.getInstance());
}

M.2 振動図形のまとめ

  • リサージュ図形の原理によって,少ないパラメータにも関わらず,中心がありそうな軌跡をなぞったようなあらゆるパターンの図形を生成できる.
  • リサージュ図形と直接関係ないが,点同士を距離に応じた濃さの線で結ぶと独特の陰影を伴ったドローイングが実現する.

results matching ""

    No results matching ""