Изменение ширины пера в соответствии со скоростью записи - PullRequest
1 голос
/ 27 июня 2011

У меня проблема с OpenGL ES при рисовании нескольких маленьких частей обводки с разным размером точки. Я пытаюсь смоделировать изменение ширины пера в соответствии со скоростью записи, ширина пера большая при медленной записи и мала при быстрой записи.Это мой код (для изучения я использовал GLPaint из исходного кода Apple):

static GLfloat*     vertexBuffer = NULL;
static NSUInteger   vertexMax = 64;
NSUInteger          vertexCount = 0,
                    count,
                    i;

[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);

// Convert locations from Points to Pixels
CGFloat scale = self.contentScaleFactor;
start.x *= scale;
start.y *= scale;
end.x *= scale;
end.y *= scale;

// Allocate vertex array buffer
if(vertexBuffer == NULL) {
    vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
}

// Add points to the buffer so there are drawing points every X pixels
count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
for(i = 0; i < count; ++i) {
    if(vertexCount == vertexMax) {
        vertexMax = 2 * vertexMax;
        vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
    }

    vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count);
    vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count);
    vertexCount += 1;
}

if (vertexCount > 0) {
    GLfloat rate = ((GLfloat)vertexCount)/MAX_BETWEEN_POINTS;
    if (rate > 0.75) {
        rate = 0.75;
    }
    GLfloat distract = penWidth * rate/vertexCount;

    for (int i = 0; i < vertexCount; i++) {
        GLfloat * smallBuf = malloc(4 * sizeof(GLfloat));
        smallBuf[0] = vertexBuffer[4*i + 0];
        smallBuf[1] = vertexBuffer[4*i + 1];
        smallBuf[2] = vertexBuffer[4*i + 2];
        smallBuf[3] = vertexBuffer[4*i + 3];

        if (lastPenWidth - distract < penWidth*0.75) {
            lastPenWidth = penWidth*0.75;
        } else {
            lastPenWidth = lastPenWidth - distract;
        }
        glPointSize(lastPenWidth);
        // Render the vertex array
        glVertexPointer(2, GL_FLOAT, 0, smallBuf);
        glDrawArrays(GL_POINTS, 0, 2);
        free(smallBuf);
    }
    NSLog(@"Vertext count: %d --- Distract: %0.2f --- Rate: %0.2f", vertexCount, distract, rate);
} else {
    glPointSize(penWidth);

    NSLog(@"Vertext count: %d", vertexCount);
    // Render the vertex array
    glVertexPointer(2, GL_FLOAT, 0, vertexBuffer);
    glDrawArrays(GL_POINTS, 0, vertexCount);
}
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

Но я получаю этот результат со многими точками в середине штриха.У вас есть идеи по этой проблеме?

enter image description here

Большое спасибо.

Ответы [ 2 ]

3 голосов
/ 31 июля 2012

Вы смотрели на плавное рисование как на этой ссылке ? Вы бы лучше изменить его в соответствии с вашими потребностями, это в cocos2d.

0 голосов
/ 25 августа 2011

Этот превосходный код AS3 из Flash & Math - приложение для плавного и отзывчивого рисования в AS3 Flash не так сложно адаптировать к любому языку по вашему выбору. Я прикрепил его, так как не у всех есть Flash.

/*
http://www.flashandmath.com/advanced/smoothdraw/index.html
By Dan Gries
www.flashandmath.com
dan A T flashandmath D O T com

*/

import com.flashandmath.dg.GUI.GradientSwatch;
import com.flashandmath.dg.bitmapUtilities.BitmapSaver;


var lineLayer:Sprite;
var lastSmoothedMouseX:Number;
var lastSmoothedMouseY:Number;
var lastMouseX:Number;
var lastMouseY:Number;
var lastThickness:Number;
var lastRotation:Number;
var lineColor:uint;
var lineThickness:Number;
var lineRotation:Number;
var L0Sin0:Number;
var L0Cos0:Number;
var L1Sin1:Number;
var L1Cos1:Number;
var sin0:Number;
var cos0:Number;
var sin1:Number;
var cos1:Number;
var dx:Number;
var dy:Number;
var dist:Number;
var targetLineThickness:Number;
var colorLevel:Number;
var targetColorLevel:Number;
var smoothedMouseX:Number;
var smoothedMouseY:Number;
var tipLayer:Sprite;
var boardBitmap:Bitmap;
var boardBitmapData:BitmapData;
var bitmapHolder:Sprite;
var boardWidth:Number;
var boardHeight:Number;
var smoothingFactor:Number;
var mouseMoved:Boolean;
var dotRadius:Number;
var startX:Number;
var startY:Number;
var undoStack:Vector.<BitmapData>;
var minThickness:Number;
var thicknessFactor:Number;
var mouseChangeVectorX:Number;
var mouseChangeVectorY:Number;
var lastMouseChangeVectorX:Number;
var lastMouseChangeVectorY:Number;

var thicknessSmoothingFactor:Number;

var bitmapSaver:BitmapSaver;

var controlVecX:Number;
var controlVecY:Number;
var controlX1:Number;
var controlY1:Number;
var controlX2:Number;
var controlY2:Number;

var tipTaperFactor:Number;

var numUndoLevels:Number;

var controlPanel:Sprite;
var swatches:Vector.<GradientSwatch>;
var swatchColors:Vector.<uint>;

var paintColorR1:Number;
var paintColorG1:Number;
var paintColorB1:Number;
var paintColorR2:Number;
var paintColorG2:Number;
var paintColorB2:Number;

var red:Number;
var green:Number;
var blue:Number;

var colorChangeRate:Number;

var panelColor:uint;

var boardMask:Sprite;

////

//Setting the following NO_SCALE parameter helps avoid strange artifacts
//in the displayed bitmaps caused by repositioning of the swf within the html page.
stage.scaleMode=StageScaleMode.NO_SCALE;

init();

////

function init():void {

    boardWidth = 700;
    boardHeight = 500;

    minThickness = 0.2;
    thicknessFactor = 0.25;

    smoothingFactor = 0.3;  //Should be set to something between 0 and 1.  Higher numbers mean less smoothing.
    thicknessSmoothingFactor = 0.3;

    dotRadius = 2; //radius for drawn dot if there is no mouse movement between mouse down and mouse up.

    tipTaperFactor = 0.8;

    numUndoLevels = 10;

    colorChangeRate = 0.05;

    panelColor = 0xAAAAAA;

    paintColorR1 = 16;
    paintColorG1 = 0;
    paintColorB1 = 0;
    paintColorR2 = 128;
    paintColorG2 = 0;
    paintColorB2 = 0;

    swatchColors = Vector.<uint>([0x100000, 0x800000,
                                  darkenColor(0xA24F31,0.5), 0xA24F31,
                                  darkenColor(0x906000,0.33), 0x906000,
                                  darkenColor(0xB48535,0.5), 0xB48535,
                                  darkenColor(0x938E60,0.75),0x938E60,
                                  darkenColor(0x6F7D4F,0.4),0x6F7D4F,
                                  0x000000, 0x226600,
                                  darkenColor(0x8FAD81, 0.75), 0x8FAD81,
                                  0x000000, 0x005077,                                 
                                  darkenColor(0x4F848A,0.5),0x4F848A,
                                  darkenColor(0x646077,0.5),0x646077,
                                  darkenColor(0x784B67,0.4),0x784B67,
                                  darkenColor(0x9A659A, 0.4), 0x9A659A,
                                  0x000000, 0x606060,
                                  0x000000, 0x000000,
                                  0xD0D0D0, 0xFFFFFF]);
    swatches = new Vector.<GradientSwatch>;

    boardBitmapData = new BitmapData(boardWidth, boardHeight, false);
    boardBitmap = new Bitmap(boardBitmapData);


    //The undo buffer will hold the previous drawing.
    //If we want more levels of undo, we would have to record several undo buffers.  We only use one
    //here for simplicity.
    undoStack = new Vector.<BitmapData>;
    bitmapHolder = new Sprite();
    lineLayer = new Sprite();

    boardMask = new Sprite();
    boardMask.graphics.beginFill(0xFF0000);
    boardMask.graphics.drawRect(0,0,boardWidth,boardHeight);
    boardMask.graphics.endFill();

    drawBackground();

    /*
    The tipLayer holds the tip portion of the line.
    Because of the smoothing technique we are using, while the user is drawing the drawn line will not
    extend all the way from the last position to the current mouse position.  We use a small 'tip' to 
    complete this line all the way to the current mouse position.
    */
    tipLayer = new Sprite();
    tipLayer.mouseEnabled = false;

    /*
    Bitmaps cannot receive mouse events.  so we add it to a holder sprite.
    */
    this.addChild(bitmapHolder);
    bitmapHolder.x = 5;
    bitmapHolder.y = 5;
    bitmapHolder.addChild(boardBitmap);
    bitmapHolder.addChild(tipLayer);
    bitmapHolder.addChild(boardMask);
    bitmapHolder.mask = boardMask;

    //We add the panel at the bottom which will hold color swatches
    controlPanel = new Sprite();
    controlPanel.graphics.beginFill(panelColor);
    controlPanel.graphics.drawRect(0, 0, boardWidth, 40);
    controlPanel.graphics.endFill();
    controlPanel.x = bitmapHolder.x;
    controlPanel.y = bitmapHolder.y + boardHeight+2;
    this.addChild(controlPanel);
    createSwatches();

    var btnErase:BtnErase = new BtnErase();
    btnErase.x = 690;
    btnErase.y = controlPanel.height/2;;
    btnErase.addEventListener(MouseEvent.CLICK, erase);
    controlPanel.addChild(btnErase);

    var btnUndo:BtnUndo = new BtnUndo();
    btnUndo.x = 620;
    btnUndo.y = controlPanel.height/2;;
    btnUndo.addEventListener(MouseEvent.CLICK, undoButtonHandler);
    controlPanel.addChild(btnUndo);

    var btnExport:BtnExport = new BtnExport();
    btnExport.x = 550;
    btnExport.y = controlPanel.height/2;;
    btnExport.addEventListener(MouseEvent.CLICK, exportHandler);
    controlPanel.addChild(btnExport);

    bitmapSaver = new BitmapSaver(boardBitmapData);
    bitmapSaver.x = 0.5*(boardWidth - bitmapSaver.width);
    bitmapSaver.y = 0.5*(boardHeight - bitmapSaver.height);

    bitmapHolder.addEventListener(MouseEvent.MOUSE_DOWN, startDraw);
    stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
}

function createSwatches():void {
    var swatchLength = Math.floor(0.8*controlPanel.height);
    var space:Number = 5;
    for (var i:Number = 0; i< swatchColors.length; i=i+2) {
        var thisSwatch:GradientSwatch = new GradientSwatch(swatchColors[i], swatchColors[i+1], 0.75*swatchLength, swatchLength);
        thisSwatch.x = (space + 0.75*swatchLength)*(i+1)/2;
        thisSwatch.y = controlPanel.height/2;
        controlPanel.addChild(thisSwatch);
        swatches.push(thisSwatch);
        thisSwatch.addEventListener(MouseEvent.CLICK, swatchClickHandler);
    }
}

function swatchClickHandler(evt:MouseEvent):void {
    var thisSwatch = evt.currentTarget;
    paintColorR1 = thisSwatch.red1;
    paintColorG1 = thisSwatch.green1;
    paintColorB1 = thisSwatch.blue1;
    paintColorR2 = thisSwatch.red2;
    paintColorG2 = thisSwatch.green2;
    paintColorB2 = thisSwatch.blue2;
}

function startDraw(evt:MouseEvent):void {
    stage.addEventListener(MouseEvent.MOUSE_UP, stopDraw);

    startX = lastMouseX = smoothedMouseX = lastSmoothedMouseX = bitmapHolder.mouseX;
    startY = lastMouseY = smoothedMouseY = lastSmoothedMouseY = bitmapHolder.mouseY;
    lastThickness = 0;
    lastRotation = Math.PI/2;
    colorLevel = 0;
    lastMouseChangeVectorX = 0;
    lastMouseChangeVectorY = 0;

    //We will keep track of whether the mouse moves in between a mouse down and a mouse up.  If not,
    //a small dot will be drawn.
    mouseMoved = false;

    stage.addEventListener(MouseEvent.MOUSE_MOVE, drawLine);
    //this.addEventListener(Event.ENTER_FRAME, drawLine);
}

function drawLine(evt:MouseEvent):void {
    mouseMoved = true;

    lineLayer.graphics.clear();

    mouseChangeVectorX = bitmapHolder.mouseX - lastMouseX;
    mouseChangeVectorY = bitmapHolder.mouseY - lastMouseY;


    //Cusp detection - if the mouse movement is more than 90 degrees
    //from the last motion, we will draw all the way out to the last
    //mouse position before proceeding.  We handle this by drawing the
    //previous tipLayer, and resetting the last smoothed mouse position
    //to the last actual mouse position.
    //We use a dot product to determine whether the mouse movement is
    //more than 90 degrees from the last motion.
    if (mouseChangeVectorX*lastMouseChangeVectorX + mouseChangeVectorY*lastMouseChangeVectorY < 0) {
        boardBitmapData.draw(tipLayer);
        smoothedMouseX = lastSmoothedMouseX = lastMouseX;
        smoothedMouseY = lastSmoothedMouseY = lastMouseY;
        lastRotation += Math.PI;
        lastThickness = tipTaperFactor*lastThickness;
    }


    //We smooth out the mouse position.  The drawn line will not extend to the current mouse position; instead
    //it will be drawn only a portion of the way towards the current mouse position.  This creates a nice
    //smoothing effect.
    smoothedMouseX = smoothedMouseX + smoothingFactor*(bitmapHolder.mouseX - smoothedMouseX);
    smoothedMouseY = smoothedMouseY + smoothingFactor*(bitmapHolder.mouseY - smoothedMouseY);

    //We determine how far the mouse moved since the last position.  We use this distance to change
    //the thickness and brightness of the line.
    dx = smoothedMouseX - lastSmoothedMouseX;
    dy = smoothedMouseY - lastSmoothedMouseY;
    dist = Math.sqrt(dx*dx + dy*dy);

    if (dist != 0) {
        lineRotation = Math.PI/2 + Math.atan2(dy,dx);
    }
    else {
        lineRotation = 0;
    }

    //We use a similar smoothing technique to change the thickness of the line, so that it doesn't
    //change too abruptly.
    targetLineThickness = minThickness+thicknessFactor*dist;
    lineThickness = lastThickness + thicknessSmoothingFactor*(targetLineThickness - lastThickness);

    /*
    The "line" being drawn is actually composed of filled in shapes.  This is what allows
    us to create a varying thickness of the line.
    */
    sin0 = Math.sin(lastRotation);
    cos0 = Math.cos(lastRotation);
    sin1 = Math.sin(lineRotation);
    cos1 = Math.cos(lineRotation);
    L0Sin0 = lastThickness*sin0;
    L0Cos0 = lastThickness*cos0;
    L1Sin1 = lineThickness*sin1;
    L1Cos1 = lineThickness*cos1;
    targetColorLevel = Math.min(1,colorChangeRate*dist);
    colorLevel = colorLevel + 0.2*(targetColorLevel - colorLevel);

    red = paintColorR1 + colorLevel*(paintColorR2 - paintColorR1);
    green = paintColorG1 + colorLevel*(paintColorG2  - paintColorG1);
    blue = paintColorB1 + colorLevel*(paintColorB2 - paintColorB1);

    lineColor = (red << 16) | (green << 8) | (blue);

    controlVecX = 0.33*dist*sin0;
    controlVecY = -0.33*dist*cos0;
    controlX1 = lastSmoothedMouseX + L0Cos0 + controlVecX;
    controlY1 = lastSmoothedMouseY + L0Sin0 + controlVecY;
    controlX2 = lastSmoothedMouseX - L0Cos0 + controlVecX;
    controlY2 = lastSmoothedMouseY - L0Sin0 + controlVecY;

    lineLayer.graphics.lineStyle(1,lineColor);
    lineLayer.graphics.beginFill(lineColor);
    lineLayer.graphics.moveTo(lastSmoothedMouseX + L0Cos0, lastSmoothedMouseY + L0Sin0);
    lineLayer.graphics.curveTo(controlX1,controlY1,smoothedMouseX + L1Cos1, smoothedMouseY + L1Sin1);
    lineLayer.graphics.lineTo(smoothedMouseX - L1Cos1, smoothedMouseY - L1Sin1);
    lineLayer.graphics.curveTo(controlX2, controlY2, lastSmoothedMouseX - L0Cos0, lastSmoothedMouseY - L0Sin0);
    lineLayer.graphics.lineTo(lastSmoothedMouseX + L0Cos0, lastSmoothedMouseY + L0Sin0);
    lineLayer.graphics.endFill();
    boardBitmapData.draw(lineLayer);

    //We draw the tip, which completes the line from the smoothed mouse position to the actual mouse position.
    //We won't actually add this to the drawn bitmap until a mouse up completes the drawing of the current line.

    //round tip:
    var taperThickness:Number = tipTaperFactor*lineThickness;
    tipLayer.graphics.clear();
    tipLayer.graphics.beginFill(lineColor);
    tipLayer.graphics.drawEllipse(bitmapHolder.mouseX - taperThickness, bitmapHolder.mouseY - taperThickness, 2*taperThickness, 2*taperThickness);
    tipLayer.graphics.endFill();
    //quad segment
    tipLayer.graphics.lineStyle(1,lineColor);
    tipLayer.graphics.beginFill(lineColor);
    tipLayer.graphics.moveTo(smoothedMouseX + L1Cos1, smoothedMouseY + L1Sin1);
    tipLayer.graphics.lineTo(bitmapHolder.mouseX + tipTaperFactor*L1Cos1, bitmapHolder.mouseY + tipTaperFactor*L1Sin1);
    tipLayer.graphics.lineTo(bitmapHolder.mouseX - tipTaperFactor*L1Cos1, bitmapHolder.mouseY - tipTaperFactor*L1Sin1);
    tipLayer.graphics.lineTo(smoothedMouseX - L1Cos1, smoothedMouseY - L1Sin1);
    tipLayer.graphics.lineTo(smoothedMouseX + L1Cos1, smoothedMouseY + L1Sin1);
    tipLayer.graphics.endFill();

    lastSmoothedMouseX = smoothedMouseX;
    lastSmoothedMouseY = smoothedMouseY;
    lastRotation = lineRotation;
    lastThickness = lineThickness;
    lastMouseChangeVectorX = mouseChangeVectorX;
    lastMouseChangeVectorY = mouseChangeVectorY;
    lastMouseX = bitmapHolder.mouseX;
    lastMouseY = bitmapHolder.mouseY;

    evt.updateAfterEvent();

}

function stopDraw(evt:MouseEvent):void {
    //If the mouse didn't move, we will draw just a dot.  Its size will be randomized.
    if (!mouseMoved) {
        var randRadius = dotRadius*(0.75+0.75*Math.random());
        var dotColor:uint = (paintColorR1 << 16) | (paintColorG1 << 8) | (paintColorB1);
        var dot:Sprite = new Sprite();
        dot.graphics.beginFill(dotColor)
        dot.graphics.drawEllipse(startX - randRadius, startY - randRadius, 2*randRadius, 2*randRadius);
        dot.graphics.endFill();
        boardBitmapData.draw(dot);
    }

    stage.removeEventListener(MouseEvent.MOUSE_MOVE, drawLine);
    stage.removeEventListener(MouseEvent.MOUSE_UP, stopDraw);

    //We add the tipLayer to complete the line all the way to the current mouse position:
    boardBitmapData.draw(tipLayer);

    //record undo bitmap and add to undo stack
    var undoBuffer:BitmapData = new BitmapData(boardWidth, boardHeight, false);
    undoBuffer.copyPixels(boardBitmapData,undoBuffer.rect,new Point(0,0));
    undoStack.push(undoBuffer);
    if (undoStack.length > numUndoLevels + 1) {
        undoStack.splice(0,1);
    }

}

function erase(evt:MouseEvent):void {
    tipLayer.graphics.clear();
    drawBackground();
}

function drawBackground():void {
    //We draw a background with a very subtle gradient effect so that the canvas darkens towards the edges.
    var gradMat:Matrix = new Matrix();
    gradMat.createGradientBox(700,500,0,0,0);
    var bg:Sprite = new Sprite();
    bg.graphics.beginGradientFill("radial",[0xDDD0AA,0xC6B689],[1,1],[1,255],gradMat);
    bg.graphics.drawRect(0,0,700,500);
    bg.graphics.endFill();
    boardBitmapData.draw(bg);

    //We clear out the undo buffer with a copy of just a blank background:
    undoStack = new Vector.<BitmapData>;
    var undoBuffer:BitmapData = new BitmapData(boardWidth, boardHeight, false);
    undoBuffer.copyPixels(boardBitmapData,undoBuffer.rect,new Point(0,0));
    undoStack.push(undoBuffer);
}

function undoButtonHandler(evt:MouseEvent):void {
    undo();
}

function keyDownListener(evt:KeyboardEvent):void {
    //Listening for Z, which will be a keyboard shortcut for undo.
    if ((evt.keyCode == 90)) {
        undo();
    }
}

function undo():void {
    if (undoStack.length > 1) {
        boardBitmapData.copyPixels(undoStack[undoStack.length - 2],boardBitmapData.rect,new Point(0,0));
        undoStack.splice(undoStack.length - 1, 1);
    }
    tipLayer.graphics.clear();
}

//this function assists with creating colors for the gradients.
function darkenColor(c:uint, factor:Number):uint {
    var r:Number = (c >> 16);
    var g:Number = (c >> 8) & 0xFF;
    var b:Number = c & 0xFF;

    var newRed:Number = Math.min(255, r*factor);
    var newGreen:Number = Math.min(255, g*factor);
    var newBlue:Number = Math.min(255, b*factor);

    return (newRed << 16) | (newGreen << 8) | (newBlue);
}

function exportHandler(evt:MouseEvent):void {
    this.addChild(bitmapSaver);
    bitmapSaver.addEventListener(BitmapSaver.BUTTON_CLICKED, closeWindow);
}

function closeWindow(evt:Event):void {
    this.removeChild(bitmapSaver);
    bitmapSaver.removeEventListener(BitmapSaver.BUTTON_CLICKED, closeWindow);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...