Willkommen auf unserem Seminar-Blog

Immer auf dem aktuellen Stand bleiben

Dieser Seminar-Blog befindet sich noch im Aufbau und wird in den kommenden Tagen entsprechend verfeinert.

Member Login

Lost your password?

Registration is closed

Sorry, you are not allowed to register by yourself on this site!

You must either be invited by one of our team member or request an invitation by email at viad.info {at} zhdk {dot} ch.

9. Draw Ornament – PGraphics, Buffer, GUI

12. November 2010

Bei dieser Aufgabe haben wir den Umgang mir Buffern gelernt und die Klasse PGraphics verwendet. Die Aufgabe bestand darin ein Zeichnungstool zu erstellen. Das Zeichnungsprogramm sollte die Möglichkeit bieten einen Screenshot zu speichern als JPG oder PNG. Ebenfalls sollte der Abgabe die interessanteste Zeichnung als File beigelegt werden. Das Programm soll auch einige GUI-Elemente enthalten, mit denen man die Zeichnung beeinflussen kann. Ich hatte hier die Idee eine vorhergehende Aufgabe weiterzuführen. Und zwar das Erstellen von Ornamenten. In einem Buffer sollte man mit der Maus zeichnen können und diese "Kachel" dann gleich in einem Displaybereich dupliziert dargestellt bekommen. Hilfslinien beim Mauszeiger sollten helfen die Linien an den Grenzen (oben, unten, links, rechts) nahtlos zu platzieren. Optionen
  • Werkzeugspitze Kreis
  • Werkzeugspitze Linie
  • Radiergummi
  • Grösse Werkzeugspitzen ändern
  • Anzeige Spalten gespiegelt
  • Anzeige Reihen gespiegelt
  • Screenshot speichern
  • Kachelgrösse anpassen
Für das Einschalten der Optionen habe ich mal meine Checkbox-Klasse verwendet, um im Code die Flags abfragen zu können (_checked==true). Bei  der Wahl der Werkzeugspitze sollten eigentlich besser Radiobuttons verwendet werden, da hier jeweils nur eine aktiv sein kann.

Prototyp

Überarbeitete Version

Der Prototyp, den ich am Freitag erstellt habe, besitzt eine Zeichenfläche, es kann zwischen Linie und Kreis bei den Werkzeugspitzen gewechselt werden, die Radiergummi-Option funktioniert soweit und ein Screenshot kann gespeichert werden. Mit dem OPENGL-Parameter aktiviert wurde zwar das Zeichnen flüssiger, aber die SGV-Bilder für die Checkboxen wurden nur noch schwarz dargestellt. Drum mal ohne.
size(1200, 900, OPENGL);
Folgende Angabe erlaubt es beim Speichern eines Screenshots der Datei einen Namen zu geben.
save(selectOutput());
Lustigerweise gibt es einen Fehler, wenn im System-Popup Cancel gedrückt wird (NullPointerException). 2. Version Bei der zweiten Version sind nun alle Option umgesetzt. Die Kacheln werden neu alle in einen Buffer gezeichnet, somit sind Überlappungen an den Rändern möglich. Wenn die Kachelgrösse geändert wird, dann kann über ein feines "kleinmaschiges" Ornament ein grosses gezeichnet werden. Ebenfalls kann nun die Grösse der Werkzeugspitze verändert werden. Die interessantesten Resultate entstehen, wenn die Reihen und Spalten gespiegelt werden.

Spalten/Reihen gespiegelt

Barcodes

Overkill

Download Finaler Code Ornament Generator (ZIP) Checkbox Klasse
class Checkbox
{
  PVector _p1; // starting point
  PVector _p2; // end point

  int     _h = 20; // height of checkbox
  int     _w = 20; // width of checkbox
  boolean    _checked; // flag checkbox selected

  // x/y of scheckbox, width/height of bg checkbox
  Checkbox(int x,int y)
  {
    //_dir = dir;
    _p1 = new PVector(x+_w*.5, y);
    _p2 = new PVector(x+_w*.5, y+_h);

    smooth(); // svg loaded for slider styling
  }

  boolean isHit(int x,int y)
  {
    if (x > _p1.x - 10 && x < _p1.x + 10 &&
      y > _p1.y && y < _p2.y) {
      return true;
    }
    else {
      return false;
    }
  }

  void draw()
  {
    pushStyle();
      drawCheckbox();
      if (_checked==true) drawKnob(false); // if preselected
    popStyle();
  }

  void drawCheckbox()
  {
    PShape checkboxBg = loadShape("checkbox_bg.svg");
    PShape checkboxEnd = loadShape("checkbox_end.svg");

    checkboxBg.rotate(radians(-90));
    checkboxEnd.rotate(radians(-90));
    shape(checkboxEnd,_p1.x-_w/1.5*.5,_p1.y+10/1.5,20/1.5,20/1.5); // end left
    shape(checkboxEnd,_p1.x-_w/1.5*.5,_p2.y+10/1.5,20/1.5,20/1.5); // end right
    shape(checkboxBg,_p1.x-_w/1.5*.5,_p2.y,_w/1.5,_h); // strip
  }

  void drawKnob(boolean hover)
  {
    PShape sliderKnob = null;
    if (hover==false) {
      sliderKnob = loadShape("checkbox_knob.svg");
    } else {
      sliderKnob = loadShape("checkbox_knob_hover.svg");
    }

    PVector dir = PVector.sub(_p1,_p2);
    PVector pos = PVector.add( _p2, PVector.mult(dir,0.5));
    shape(sliderKnob,pos.x-_w*.5, pos.y - 10,20,20);
  }

  void mousePos(int x,int y,boolean pressed)
  {
    if(pressed)
    {
      if(isHit(x,y))
      {
        if (_checked==false) {
          drawKnob(false); // checkbox activated
          _checked = true;
        } else {
          _checked = false;
        }
      }
    }
    else
    {  // mouse over: highlight knob, only if knob visible
      if(isHit(x,y) && _checked==true) drawKnob(true);
    }
  }
}
Programm Code
// buttons
Checkbox btnSaveScreen = null;
Checkbox btnSelectDrawType0 = null;
Checkbox btnSelectDrawType1 = null;
Checkbox btnFlipXDisplay = null;
Checkbox btnFlipYDisplay = null;
Checkbox btnEraser = null;
Slider sliderBrushSize = null;
Slider sliderNumTiles = null;

PGraphics pg; // buffer drawing
PGraphics pdisp; // buffer display
// general margins
int marginLeft = 50;
int marginTop = 50;
// dimensions areas, tile
int drawAreaWidth = 200;
int drawAreaHeight = drawAreaWidth;
int tileDisplayWidth = 50;
int tileDisplayHeight = tileDisplayWidth;
int displayAreaWidth = 800;
int displayAreaHeight = displayAreaWidth;
int displayTilesX = displayAreaWidth/tileDisplayWidth;
int displayTilesY = displayTilesX; // yet square
int indexMousePointerWidth = 20; // circle following mouse pointer
int indexMousePointerHeight = indexMousePointerWidth;
int eraserWidth = indexMousePointerWidth*2;
int eraserHeight = indexMousePointerHeight*2;

int offsetTopNav = marginTop+drawAreaHeight+50; // start Y of navigation area
int offsetLeftNav = marginLeft+20; // start Y of navigation area
int drawType = 0; // 0: ellipse, 1: line
boolean mouseDown = false; // mouse pressed
boolean mouseDownDrawing = false; // draw only when mouse pressed (true: mouse pressed & within drawing area)
float pgScaleDown = 1; // pg scaled gives very thin lines (shadowy)
int fontSize = 16;

void setup()
{
  size(1200, 900); // , OPENGL)
  smooth();

  // load font and set font size
  PFont font = loadFont("AvenirLT-Roman-48.vlw");
  textFont(font, fontSize);

  // buffer erstellen
  pg = createGraphics(drawAreaWidth,drawAreaHeight,JAVA2D);
  pg.beginDraw(); pg.endDraw(); // avoid nullpointer error (has to be called once, only set on mousepressed)
  pdisp = createGraphics(displayTilesX*tileDisplayWidth,displayTilesY*tileDisplayHeight,JAVA2D);
  pdisp.beginDraw(); pdisp.endDraw(); // avoid nullpointer error (has to be called once, only set on mousepressed)

  // buttons (checkboxes)
  btnSaveScreen = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*17+4,false);
  btnSelectDrawType0 = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*0+4,true);
  btnSelectDrawType1 = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*2+4,false);
  btnFlipXDisplay = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*10+4,false);
  btnFlipYDisplay = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*12+4,false);
  btnEraser = new Checkbox(offsetLeftNav,offsetTopNav+fontSize*4+4,false);
  // sliders
  sliderBrushSize = new Slider(offsetLeftNav,offsetTopNav+fontSize*6+4,100,20,.2,true);
  sliderNumTiles = new Slider(offsetLeftNav,offsetTopNav+fontSize*21+4,100,20,.125,true);

  frameRate(30);
}

void draw()
{
  background(212);
  pushStyle();
    fill(0);
    text("Werkzeugspitze",offsetLeftNav,offsetTopNav-12);
    // BUTTON draw type LINE
    btnSelectDrawType0.draw();
    btnSelectDrawType0.mousePos(mouseX,mouseY,false);
    text("Kreis",offsetLeftNav+30,offsetTopNav+fontSize);
    btnSelectDrawType1.draw();
    btnSelectDrawType1.mousePos(mouseX,mouseY,false);
    text("Linie",offsetLeftNav+30,offsetTopNav+fontSize*3);
    // BUTTON eraser
    btnEraser.draw();
    btnEraser.mousePos(mouseX,mouseY,false);
    text("Radiergummi",offsetLeftNav+30,offsetTopNav+fontSize*5);
    // BUTTON brush size
    sliderBrushSize.draw();
    sliderBrushSize.mousePos(mouseX,mouseY,false);
    text("Grösse",offsetLeftNav+116,offsetTopNav+fontSize*7);
    // BUTTON flip display tiles X
    btnFlipXDisplay.draw();
    btnFlipXDisplay.mousePos(mouseX,mouseY,false);
    text("Spalten spiegeln",offsetLeftNav+30,offsetTopNav+fontSize*11);
    // BUTTON flip display tiles Y
    btnFlipYDisplay.draw();
    btnFlipYDisplay.mousePos(mouseX,mouseY,false);
    text("Reihen spiegeln",offsetLeftNav+30,offsetTopNav+fontSize*13);
    // BUTTON save screenshot
    btnSaveScreen.draw();
    btnSaveScreen.mousePos(mouseX,mouseY,false);
    text("Screenshot speichern",offsetLeftNav+30,offsetTopNav+fontSize*18);
    // BUTTON numbner of tiles
    sliderNumTiles.draw();
    sliderNumTiles.mousePos(mouseX,mouseY,false);
    text("Kachelgrösse",offsetLeftNav+116,offsetTopNav+fontSize*22);
  popStyle();

  tileDisplayWidth = ((int)((sliderNumTiles._pos*400.0))/50)*50;
  if (tileDisplayWidth<50) tileDisplayWidth=25;
  if (tileDisplayWidth>200) tileDisplayWidth=400;
  if (tileDisplayWidth>100 && tileDisplayWidth<200) tileDisplayWidth=100;
  //println(tileDisplayWidth);
  tileDisplayHeight = tileDisplayWidth;

  // DRAWING AREA
  // GRAPHIC drawing area
  if (mouseDownDrawing==true)  {
    pg.beginDraw();
    drawTile(pg, 1.0, 0, 0, false, false, 0, 0); // TILE
    pg.endDraw();
  }
  // border drawing area
  pushStyle();
    stroke(0);
    fill(255);
    rect(50,50,drawAreaWidth,drawAreaWidth);
  popStyle();
  pushMatrix();
    translate(0,0);
    // show buffer drawing
    image(pg, marginLeft, marginTop);
  popMatrix();
  pushStyle();
    stroke(0);
    noFill();
    rect(50,50,drawAreaWidth,drawAreaWidth);
  popStyle();

  // DISPLAY AREA
  // GRAPHIC display area (scaled down, multiplied)
  // border display area
  pushStyle();
    stroke(236);
    strokeWeight(5);
    fill(255);
    rect(300,marginLeft,displayAreaWidth,displayAreaHeight);
  popStyle();

  if (mouseDownDrawing==true)  {
    pdisp.beginDraw();
    for(int i=0; i<displayTilesX; i++) {
      for(int k=0; k<displayTilesY; k++) {
        drawTile(pdisp, (tileDisplayWidth*1.0/drawAreaWidth*1.0),tileDisplayWidth*i,tileDisplayHeight*k,btnFlipXDisplay._checked,btnFlipYDisplay._checked, i, k); // TILE SMALL
      }
    }
   pdisp.endDraw();
  }

  pushMatrix();
    translate(0,0);
    // show buffer display area
    image(pdisp, 300, marginTop);
  popMatrix();

  // OPTION save screenshot
  if (btnSaveScreen._checked==true) {
    save(selectOutput());
    btnSaveScreen._checked = false; // diactivate btn immediately (save screen only once)
  }

  // index mouse pointer
  if (hitTest(mouseX, mouseY)) { // only visible when mouse within drawing area
    pushStyle();
      noFill();
      stroke(102);
      strokeWeight(2);
      ellipse(mouseX,mouseY,indexMousePointerWidth,indexMousePointerHeight);
    popStyle();

    // help lines
    pushStyle();
      noFill();
      stroke(153);
      strokeWeight(1);
      int helplineX = mouseX;
      if (mouseX<marginLeft) helplineX = marginLeft;
      if (mouseX>marginLeft+drawAreaWidth) helplineX = marginLeft+drawAreaWidth;
      line(helplineX,marginTop,helplineX,marginTop+drawAreaHeight);
      int helplineY = mouseY;
      if (mouseY<marginTop) helplineY = marginTop;
      if (mouseY>marginTop+drawAreaHeight) helplineY = marginTop+drawAreaHeight;
      line(marginLeft,helplineY,marginLeft+drawAreaWidth,helplineY);
    popStyle();
  }
}

boolean hitTest(int x,int y)
{
  if (x > marginLeft && x < marginLeft + drawAreaWidth &&
      y > marginTop && y < marginTop + drawAreaHeight) {
    return true;
  } else {
     return false;
  }
}

// mouse events
void mousePressed()
{
  mouseDown = true;
  if (hitTest(mouseX, mouseY)) mouseDownDrawing = true; // draw only when mouse is pressed
  btnSaveScreen.mousePos(mouseX,mouseY,true);
  btnSelectDrawType0.mousePos(mouseX,mouseY,true);
  btnSelectDrawType1.mousePos(mouseX,mouseY,true);
  btnFlipXDisplay.mousePos(mouseX,mouseY,true);
  btnFlipYDisplay.mousePos(mouseX,mouseY,true);
  btnEraser.mousePos(mouseX,mouseY,true);
  sliderBrushSize.mousePos(mouseX,mouseY,true);
  sliderNumTiles.mousePos(mouseX,mouseY,true);
}
void mouseDragged()
{
  sliderBrushSize.mousePos(mouseX,mouseY,true);
  sliderNumTiles.mousePos(mouseX,mouseY,true);
}
void mouseReleased()
{
  mouseDown = false; // stop drawing
  mouseDownDrawing = false; // stop drawing
}

// FUNCTION: DRAW TILE
// scale: 1.0 -> width 200
// flipAlp: kacheln alternierend spiegeln
void drawTile(PGraphics pg, float scaling, int offsetLeft, int offsetTop, boolean flipAltX, boolean flipAltY, int numIndexCol, int numIndexRow) {

  // draws one tile, used for drawing area and display area
  // tiles in display area are scaled down
  pg.smooth();
  pg.stroke(0);
  pg.strokeWeight(1.0*scaling);
  pg.scale(pgScaleDown); // scale down

  // TODO INCLUDE TYPES
  if (mouseDownDrawing==true) {

    // SWITCH draw types
    if (btnEraser._checked==true) {
      drawType = 2;
    } else if (btnSelectDrawType0._checked==true) {
      drawType = 0;
    } else if (btnSelectDrawType1._checked==true) {
      drawType = 1;
    } else {
      drawType = 0;
    }

    float drawLineWidth = 75*sliderBrushSize._pos;
    float startX = ((mouseX/pgScaleDown-marginLeft/pgScaleDown)*scaling+offsetLeft);
    float startY = ((mouseY/pgScaleDown-marginTop/pgScaleDown)*scaling+offsetTop);
    float endX = (mouseX/pgScaleDown-(marginLeft-drawLineWidth)/pgScaleDown)*scaling+offsetLeft;
    float endY = (mouseY/pgScaleDown-(marginTop-drawLineWidth)/pgScaleDown)*scaling+offsetTop;

    int isEvenCol = numIndexCol%2;
    int isEvenRow = numIndexRow%2;
    if (flipAltX==true && isEvenCol==1) { // flip horizontally alternated
      startX = ((offsetLeft+tileDisplayWidth) - (mouseX/pgScaleDown-marginLeft/pgScaleDown)*scaling);
      endX = (offsetLeft+tileDisplayWidth) - (mouseX/pgScaleDown-(marginLeft-drawLineWidth)/pgScaleDown)*scaling;
    }
    if (flipAltY==true && isEvenRow==1) { // flip vertically alternated
      startY = ((offsetTop+tileDisplayHeight) - (mouseY/pgScaleDown-marginLeft/pgScaleDown)*scaling);
      endY = (offsetTop+tileDisplayHeight) - (mouseY/pgScaleDown-(marginTop-drawLineWidth)/pgScaleDown)*scaling;
    }

    switch(drawType) {
      case 0: // ellipse
        pushStyle();
          pg.fill(0);
          pg.noStroke();
          pg.ellipse(startX, startY,20*scaling*sliderBrushSize._pos, 20*scaling*sliderBrushSize._pos);
        popStyle();
        break;
      case 1: // line
        pg.line(startX, startY, endX, endY);
        break;
      case 2: // eraser
        pushStyle();
          pg.fill(255);
          pg.noStroke();
          pg.ellipse(startX, startY, eraserWidth*scaling*sliderBrushSize._pos, eraserHeight*scaling*sliderBrushSize._pos);
        popStyle();
        break;
    }
  }

}