void EJCanvasContext::pushQuad(EJVector2 v1, EJVector2 v2, EJVector2 v3, EJVector2 v4, EJVector2 t1, EJVector2 t2, EJVector2 t3, EJVector2 t4, EJColorRGBA color, CGAffineTransform transform)
{
	if( vertexBufferIndex >= EJ_CANVAS_VERTEX_BUFFER_SIZE - 6 ) {
		flushBuffers();
	}
	
	if( !CGAffineTransformIsIdentity(transform) ) {
		v1 = EJVector2ApplyTransform( v1, transform );
		v2 = EJVector2ApplyTransform( v2, transform );
		v3 = EJVector2ApplyTransform( v3, transform );
		v4 = EJVector2ApplyTransform( v4, transform );
	}
	
	EJVertex * vb = &CanvasVertexBuffer[vertexBufferIndex];

	EJVertex vb_0 = { v1, t1, color };
	EJVertex vb_1 = { v2, t2, color };
	EJVertex vb_2 = { v3, t3, color };
	EJVertex vb_3 = { v2, t2, color };
	EJVertex vb_4 = { v3, t3, color };
	EJVertex vb_5 = { v4, t4, color };

	vb[0] = vb_0;
	vb[1] = vb_1;
	vb[2] = vb_2;
	vb[3] = vb_3;
	vb[4] = vb_4;
	vb[5] = vb_5;
	
	vertexBufferIndex += 6;
}
void EJCanvasContext::pushTri(float x1, float y1, float x2, float y2, float x3, float y3, EJColorRGBA color, CGAffineTransform transform)
{
	if( vertexBufferIndex >= EJ_CANVAS_VERTEX_BUFFER_SIZE - 3 ) {
		flushBuffers();
	}
	
	EJVector2 d1 = { x1, y1 };
	EJVector2 d2 = { x2, y2 };
	EJVector2 d3 = { x3, y3 };
	
	if( !CGAffineTransformIsIdentity(transform) ) {
		d1 = EJVector2ApplyTransform( d1, transform );
		d2 = EJVector2ApplyTransform( d2, transform );
		d3 = EJVector2ApplyTransform( d3, transform );
	}
	
	EJVertex * vb = &CanvasVertexBuffer[vertexBufferIndex];

	EJVertex vb_0 = {d1, {0.5, 1}, color};
	EJVertex vb_1 = { d2, {0.5, 0.5}, color };
	EJVertex vb_2 = { d3, {0.5, 1}, color };

	vb[0] = vb_0;
	vb[1] = vb_1;
	vb[2] = vb_2;
	
	vertexBufferIndex += 3;
}
void EJCanvasContext::pushRect(float x, float y, float w, float h, float tx, float ty, float tw, float th, EJColorRGBA color, CGAffineTransform transform)
{

	if( vertexBufferIndex >= EJ_CANVAS_VERTEX_BUFFER_SIZE - 6 ) {
		flushBuffers();
	}
	
	EJVector2 d11 = { x, y };
	EJVector2 d21 = { x+w, y };
	EJVector2 d12 = { x, y+h };
	EJVector2 d22 = { x+w, y+h };
	
	if( !CGAffineTransformIsIdentity(transform) ) {
		d11 = EJVector2ApplyTransform( d11, transform );
		d21 = EJVector2ApplyTransform( d21, transform );
		d12 = EJVector2ApplyTransform( d12, transform );
		d22 = EJVector2ApplyTransform( d22, transform );
	}
	
	EJVertex * vb = &CanvasVertexBuffer[vertexBufferIndex];

	EJVertex vb_0 = { d11, {tx, ty}, color };	// top left
	EJVertex vb_1 = { d21, {tx+tw, ty}, color };	// top right
	EJVertex vb_2 = { d12, {tx, ty+th}, color };	// bottom left

	EJVertex vb_3 = { d21, {tx+tw, ty}, color };	// top right
	EJVertex vb_4 = { d12, {tx, ty+th}, color };	// bottom left
	EJVertex vb_5 = { d22, {tx+tw, ty+th}, color };// bottom right

	vb[0] = vb_0;	// top left
	vb[1] = vb_1;	// top right
	vb[2] = vb_2;	// bottom left
		
	vb[3] = vb_3;	// top right
	vb[4] = vb_4;	// bottom left
	vb[5] = vb_5;// bottom right
	
	vertexBufferIndex += 6;
}
Beispiel #4
0
FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
{
    // It is not enough just to round to pixels in device space. The rotation part of the
    // affine transform matrix to device space can mess with this conversion if we have a
    // rotating image like the hands of the world clock widget. We just need the scale, so
    // we get the affine transform matrix and extract the scale.

    if (m_data->m_userToDeviceTransformKnownToBeIdentity)
        return rect;

    CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
    if (CGAffineTransformIsIdentity(deviceMatrix)) {
        m_data->m_userToDeviceTransformKnownToBeIdentity = true;
        return rect;
    }

    float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
    float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);

    CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
    CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
        (rect.y() + rect.height()) * deviceScaleY);

    deviceOrigin.x = roundf(deviceOrigin.x);
    deviceOrigin.y = roundf(deviceOrigin.y);
    deviceLowerRight.x = roundf(deviceLowerRight.x);
    deviceLowerRight.y = roundf(deviceLowerRight.y);

    // Don't let the height or width round to 0 unless either was originally 0
    if (deviceOrigin.y == deviceLowerRight.y && rect.height())
        deviceLowerRight.y += 1;
    if (deviceOrigin.x == deviceLowerRight.x && rect.width())
        deviceLowerRight.x += 1;

    FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
    FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
    return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
}
Beispiel #5
0
bool AffineTransform::isIdentity() const
{
    return CGAffineTransformIsIdentity(m_transform);
}
Beispiel #6
0
void EJPath::drawLinesToContext(EJCanvasContext * context) {
    endSubPath();

    EJCanvasState * state = context->state;
    EJVector2 vecZero = { 0.0f, 0.0f };

    // Find the width of the line as it is projected onto the screen.
    float projectedLineWidth = CGAffineTransformGetScale( state->transform ) * state->lineWidth;
    context->setTexture(NULL);

    // Figure out if we need to add line caps and set the cap texture coord for square or round caps.
    // For thin lines we disable texturing and line caps.
    float width2 = state->lineWidth/2;
    BOOL addCaps = (projectedLineWidth > 2 && (state->lineCap == kEJLineCapRound || state->lineCap == kEJLineCapSquare));

    // The miter limit is the maximum allowed ratio of the miter length to half the line width.
    BOOL addMiter = (state->lineJoin == kEJLineJoinMiter);
    float miterLimit = (state->miterLimit * width2);

    EJColorRGBA color = state->strokeColor;
    color.rgba.a = (unsigned char)(color.rgba.a * state->globalAlpha);

    // Enable stencil test when drawing transparent lines.
    // Cycle through all bits, so that the stencil buffer only has to be cleared after eight stroke operations
    if( color.rgba.a < 0xff ) {
        context->flushBuffers();
        context->createStencilBufferOnce();

        glEnable(GL_STENCIL_TEST);

        glStencilMask(stencilMask);

        glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
        glStencilFunc(GL_NOTEQUAL, stencilMask, stencilMask);
    }

    // To draw the line correctly with transformations, we need to construct the line
    // vertices from the untransformed points and only apply the transformation in
    // the last step (pushQuad) again.
    CGAffineTransform inverseTransform = CGAffineTransformIsIdentity(transform)
                                         ? transform
                                         : CGAffineTransformInvert(transform);


    // Oh god, I'm so sorry... This code sucks quite a bit. I'd be surprised if I
    // will understand what I've written in 3 days :/
    // Calculating line miters for potentially closed paths is serious business!
    // And it doesn't even handle all the edge cases.

    EJVector2
    *transCurrent, *transNext,	// Pointers to current and next vertices on the line
    current, next,				// Untransformed current and next points
    firstMiter1, firstMiter2,	// First miter vertices (left, right) needed for closed paths
    miter11, miter12,			// Current miter vertices (left, right)
    miter21, miter22,			// Next miter vertices (left, right)
    currentEdge, currentExt,	// Current edge and its normal * width/2
    nextEdge = { 0.0f, 0.0f }, nextExt = { 0.0f, 0.0f };			// Next edge and its normal * width/2

    for( path_t::iterator sp = paths.begin(); sp != paths.end(); ++sp ) {
        BOOL subPathIsClosed = sp->isClosed;
        BOOL ignoreFirstSegment = addMiter && subPathIsClosed;
        BOOL firstInSubPath = true;
        BOOL miterLimitExceeded = false, firstMiterLimitExceeded = false;

        transCurrent = transNext = NULL;

        // If this subpath is closed, initialize the first vertex for the loop ("next")
        // to the last vertex in the subpath. This way, the miter between the last and
        // the first segment will be computed and used to draw the first segment's first
        // miter, as well as the last segment's last miter outside the loop.
        if( addMiter && subPathIsClosed ) {
            transNext = &sp->points.at(sp->points.size()-2);
            next = EJVector2ApplyTransform( *transNext, inverseTransform );
        }

        for( points_t::iterator vertex = sp->points.begin(); vertex != sp->points.end(); ++vertex) {
            transCurrent = transNext;
            transNext = &(*vertex);

            current = next;
            next = EJVector2ApplyTransform( *transNext, inverseTransform );

            if( !transCurrent ) {
                continue;
            }

            currentEdge	= nextEdge;
            currentExt = nextExt;
            nextEdge = EJVector2Normalize(EJVector2Sub(next, current));
            nextExt = EJVector2Make( -nextEdge.y * width2, nextEdge.x * width2 );

            if( firstInSubPath ) {
                firstMiter1 = miter21 = EJVector2Add( current, nextExt );
                firstMiter2 = miter22 = EJVector2Sub( current, nextExt );
                firstInSubPath = false;

                // Start cap
                if( addCaps && !subPathIsClosed ) {
                    if( state->lineCap == kEJLineCapSquare ) {
                        EJVector2 capExt = { -nextExt.y, nextExt.x };
                        EJVector2 cap11 = EJVector2Add( miter21, capExt );
                        EJVector2 cap12 = EJVector2Add( miter22, capExt );

                        context->
                        pushQuad(cap11 ,cap12 ,miter21 ,miter22
                                 ,vecZero ,vecZero ,vecZero ,vecZero
                                 ,color ,transform);
                    }
                    else {
                        drawArcToContext(context, current, miter22, miter21, color);
                    }
                }

                continue;
            }


            miter11 = miter21;
            miter12 = miter22;

            BOOL miterAdded = false;
            if( addMiter ) {
                EJVector2 miterEdge = EJVector2Add( currentEdge, nextEdge );
                float miterExt = (1/EJVector2Dot(miterEdge, miterEdge)) * state->lineWidth;

                if( miterExt < miterLimit ) {
                    miterEdge.x *= miterExt;
                    miterEdge.y *= miterExt;
                    miter21 = EJVector2Make( current.x - miterEdge.y, current.y + miterEdge.x );
                    miter22 = EJVector2Make( current.x + miterEdge.y, current.y - miterEdge.x );

                    miterAdded = true;
                    miterLimitExceeded = false;
                }
                else {
                    miterLimitExceeded = true;
                }
            }

            // No miter added? Calculate the butt for the current segment
            if( !miterAdded ) {
                miter21 = EJVector2Add(current, currentExt);
                miter22 = EJVector2Sub(current, currentExt);
            }

            if( ignoreFirstSegment ) {
                // True when starting from the back vertex of a closed path. This run was just
                // to calculate the first miter.
                firstMiter1 = miter21;
                firstMiter2 = miter22;
                if( !miterAdded ) {
                    // Flip miter21 <> miter22 if it's the butt for the first segment
                    miter21 = firstMiter2;
                    miter22 = firstMiter1;
                }
                firstMiterLimitExceeded = miterLimitExceeded;
                ignoreFirstSegment = false;
                continue;
            }

            if( !addMiter || miterLimitExceeded ) {
                // previous point can be approximated, good enough for distance comparison
                EJVector2 prev = EJVector2Sub(current, currentEdge);
                EJVector2 p1, p2;
                float d1, d2;

                // calculate points to use for bevel
                // two points are possible for each edge - the one farthest away from the other line has to be used

                // calculate point for current edge
                d1 = EJDistanceToLineSegmentSquared(miter21, current, next);
                d2 = EJDistanceToLineSegmentSquared(miter22, current, next);
                p1 = ( d1 > d2 ) ? miter21 : miter22;

                // calculate point for next edge
                d1 = EJDistanceToLineSegmentSquared(EJVector2Add(current, nextExt), current, prev);
                d2 = EJDistanceToLineSegmentSquared(EJVector2Sub(current, nextExt), current, prev);
                p2 = ( d1 > d2 ) ? EJVector2Add(current, nextExt) : EJVector2Sub(current, nextExt);

                if( state->lineJoin==kEJLineJoinRound ) {
                    drawArcToContext(context, current, p1, p2, color);
                }
                else {
                    context->
                    pushTri(p1.x	,p1.y ,current.x,current.y ,p2.x ,p2.y
                            ,color ,transform);
                }
            }

            context->
            pushQuad(miter11 ,miter12 ,miter21 ,miter22
                     ,vecZero ,vecZero ,vecZero ,vecZero
                     ,color ,transform);

            // No miter added? The "miter" for the next segment needs to be the butt for the next segment,
            // not the butt for the current one.
            if( !miterAdded ) {
                miter21 = EJVector2Add(current, nextExt);
                miter22 = EJVector2Sub(current, nextExt);
            }
        } // for each subpath


        // The last segment, not handled in the loop
        if( !firstMiterLimitExceeded && addMiter && subPathIsClosed ) {
            miter11 = firstMiter1;
            miter12 = firstMiter2;
        }
        else {
            EJVector2 untransformedBack = EJVector2ApplyTransform(sp->points.back(), inverseTransform);
            miter11 = EJVector2Add(untransformedBack, nextExt);
            miter12 = EJVector2Sub(untransformedBack, nextExt);
        }

        if( (!addMiter || firstMiterLimitExceeded) && subPathIsClosed ) {
            float d1,d2;
            EJVector2 p1,p2,
                      firstNormal = EJVector2Sub(firstMiter1,firstMiter2),							// unnormalized line normal for first edge
                      second		= EJVector2Add(next,EJVector2Make(firstNormal.y,-firstNormal.x));	// approximated second point

            // calculate points to use for bevel
            // two points are possible for each edge - the one farthest away from the other line has to be used

            // calculate point for current edge
            d1 = EJDistanceToLineSegmentSquared(miter12, next, second);
            d2 = EJDistanceToLineSegmentSquared(miter11, next, second);
            p2 = ( d1 > d2 )?miter12:miter11;

            // calculate point for next edge
            d1 = EJDistanceToLineSegmentSquared(firstMiter1, current, next);
            d2 = EJDistanceToLineSegmentSquared(firstMiter2, current, next);
            p1 = (d1>d2)?firstMiter1:firstMiter2;

            if( state->lineJoin==kEJLineJoinRound ) {
                drawArcToContext(context, next, p1, p2, color);
            }
            else {
                context->
                pushTri(p1.x	,p1.y ,next.x ,next.y ,p2.x,p2.y
                        ,color ,transform);
            }
        }

        context->
        pushQuad(miter11,miter12,miter21,miter22
                 ,vecZero ,vecZero ,vecZero ,vecZero
                 ,color ,transform);

        // End cap
        if( addCaps && !subPathIsClosed ) {
            if( state->lineCap == kEJLineCapSquare ) {
                EJVector2 capExt = { nextExt.y, -nextExt.x };
                EJVector2 cap11 = EJVector2Add( miter11, capExt );
                EJVector2 cap12 = EJVector2Add( miter12, capExt );

                context->
                pushQuad(cap11,cap12,miter11,miter12
                         ,vecZero,vecZero,vecZero ,vecZero
                         ,color ,transform);
            }
            else {
                drawArcToContext(context, next, miter11, miter12, color);
            }
        }
    } // for each path

    // disable stencil test when drawing transparent lines
    if( color.rgba.a < 0xff ) {
        context->flushBuffers();
        glDisable(GL_STENCIL_TEST);

        if( stencilMask == (1<<7) ) {
            stencilMask = (1<<0);

            glStencilMask(0xff);
            glClearStencil(0x0);
            glClear(GL_STENCIL_BUFFER_BIT);
        }
        else {
            stencilMask <<= 1;
        }
    }
}