bool GiCanvasIos::drawImage(CGImageRef image, const Point2d& centerM, bool autoScale) { CGContextRef context = m_draw->getContext(); bool ret = false; if (context && image) { Point2d ptD = centerM * m_draw->xf().modelToDisplay(); float w = CGImageGetWidth(image); float h = CGImageGetHeight(image); if (autoScale) { w *= m_draw->xf().getViewScale(); h *= m_draw->xf().getViewScale(); } CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height()); af = CGAffineTransformTranslate(af, ptD.x - w * 0.5f, m_draw->height() - (ptD.y + h * 0.5f)); CGContextConcatCTM(context, af); CGContextDrawImage(context, CGRectMake(0, 0, w, h), image); CGContextConcatCTM(context, CGAffineTransformInvert(af)); ret = true; } return ret; }
void GiCanvasIos::endPaint(bool draw) { if (m_draw->getContext()) { if (draw && m_draw->_buffctx && m_draw->_context) { CGContextRef context = m_draw->_context; CGImageRef image = CGBitmapContextCreateImage(m_draw->_buffctx); CGRect rect = CGRectMake(0, 0, m_draw->width(), m_draw->height()); // 逻辑宽高点数 if (image) { CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height()); CGContextConcatCTM(context, af); // 图像是朝上的,上下文坐标系朝下,上下颠倒显示 CGInterpolationQuality old = CGContextGetInterpolationQuality(context); CGContextSetInterpolationQuality(context, kCGInterpolationNone); CGContextDrawImage(context, rect, image); CGContextSetInterpolationQuality(context, old); CGContextConcatCTM(context, CGAffineTransformInvert(af)); // 恢复成坐标系朝下 CGImageRelease(image); } } if (m_draw->_buffctx) { CGContextRelease(m_draw->_buffctx); m_draw->_buffctx = NULL; } m_draw->_context = NULL; if (owner()) owner()->_endPaint(); } }
bool GiCanvasIos::drawCachedBitmap2(const GiCanvas* p, float x, float y, bool secondBmp) { bool ret = false; if (p && p->getCanvasType() == getCanvasType()) { GiCanvasIos* gs = (GiCanvasIos*)p; int index = secondBmp ? 1 : 0; CGImageRef image = gs->m_draw->_cacheserr[index] ? NULL : gs->m_draw->_caches[index]; CGContextRef context = m_draw->getContext(); if (context && image) { CGRect rect = CGRectMake(x, y, m_draw->width(), m_draw->height()); CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height()); CGContextConcatCTM(context, af); CGInterpolationQuality oldQuality = CGContextGetInterpolationQuality(context); CGContextSetInterpolationQuality(context, kCGInterpolationNone); CGContextDrawImage(context, rect, image); CGContextSetInterpolationQuality(context, oldQuality); CGContextConcatCTM(context, CGAffineTransformInvert(af)); ret = true; } } return ret; }
CGImageRef GiCanvasIos::cachedBitmap(bool invert) { CGImageRef image = m_draw->_caches[0]; if (!image || !invert) return image; // 调用者不能释放图像 size_t w = CGImageGetWidth(image); // 图像宽度,像素单位,不是点单位 size_t h = CGImageGetHeight(image); CGImageRef newimg = NULL; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, w * 4, colorSpace, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(colorSpace); if (context) { CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, h); CGContextConcatCTM(context, af); // 图像是朝上的,上下文坐标系朝下,上下颠倒显示 CGContextDrawImage(context, CGRectMake(0, 0, w, h), image); CGContextConcatCTM(context, CGAffineTransformInvert(af)); newimg = CGBitmapContextCreateImage(context); // 得到上下颠倒的新图像 CGContextRelease(context); } return newimg; // 由调用者释放图像, CGImageRelease }
void wkSetPatternPhaseInUserSpace(CGContextRef context, CGPoint phasePoint) { CGAffineTransform userToBase = CGAffineTransformConcat(CGContextGetCTM(context), CGAffineTransformInvert(CGContextGetBaseCTM(context))); CGPoint phase = CGPointApplyAffineTransform(phasePoint, userToBase); CGContextSetPatternPhase(context, CGSizeMake(phase.x, phase.y)); }
JNIEXPORT void JNICALL OS_NATIVE(CGAffineTransformInvert) (JNIEnv *env, jclass that, jfloatArray arg0, jfloatArray arg1) { jfloat *lparg0=NULL; jfloat *lparg1=NULL; OS_NATIVE_ENTER(env, that, CGAffineTransformInvert_FUNC); if (arg0) if ((lparg0 = (*env)->GetFloatArrayElements(env, arg0, NULL)) == NULL) goto fail; if (arg1) if ((lparg1 = (*env)->GetFloatArrayElements(env, arg1, NULL)) == NULL) goto fail; *(CGAffineTransform *)lparg1 = CGAffineTransformInvert(*(CGAffineTransform *)lparg0); fail: if (arg1 && lparg1) (*env)->ReleaseFloatArrayElements(env, arg1, lparg1, 0); if (arg0 && lparg0) (*env)->ReleaseFloatArrayElements(env, arg0, lparg0, JNI_ABORT); OS_NATIVE_EXIT(env, that, CGAffineTransformInvert_FUNC); }
CFX_QuartzDeviceDriver::CFX_QuartzDeviceDriver(CGContextRef context, FX_INT32 deviceClass) { m_saveCount = 0; _context = context; _deviceClass = deviceClass; CGContextRetain(_context); CGRect r = CGContextGetClipBoundingBox(context); _width = FXSYS_round(r.size.width); _height = FXSYS_round(r.size.height); _renderCaps = FXRC_SOFT_CLIP | FXRC_BLEND_MODE | FXRC_ALPHA_PATH | FXRC_ALPHA_IMAGE | FXRC_BIT_MASK | FXRC_ALPHA_MASK; if (_deviceClass != FXDC_DISPLAY) { } else { CGImageRef image = CGBitmapContextCreateImage(_context); if (image) { _renderCaps |= FXRC_GET_BITS; _width = CGImageGetWidth(image); _height = CGImageGetHeight(image); CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image); if (kCGImageAlphaPremultipliedFirst == alphaInfo || kCGImageAlphaPremultipliedLast == alphaInfo || kCGImageAlphaOnly == alphaInfo) { _renderCaps |= FXRC_ALPHA_OUTPUT; } } CGImageRelease(image); } CGAffineTransform ctm = CGContextGetCTM(_context); CGContextSaveGState(_context); m_saveCount++; if (ctm.d >= 0) { CGFloat offset_x, offset_y; offset_x = ctm.tx; offset_y = ctm.ty; CGContextTranslateCTM(_context, -offset_x, -offset_y); CGContextConcatCTM(_context, CGAffineTransformMake(1, 0, 0, -1, offset_x, _height + offset_y)); } _foxitDevice2User = CGAffineTransformIdentity; _user2FoxitDevice = CGAffineTransformInvert(_foxitDevice2User); }
bool GiCanvasIos::drawImage(CGImageRef image, const Box2d& rectM) { CGContextRef context = m_draw->getContext(); bool ret = false; if (context && image) { Point2d ptD = rectM.center() * m_draw->xf().modelToDisplay(); Box2d rect = rectM * m_draw->xf().modelToDisplay(); CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height()); af = CGAffineTransformTranslate(af, ptD.x - rect.width() * 0.5f, m_draw->height() - (ptD.y + rect.height() * 0.5f)); CGContextConcatCTM(context, af); CGContextDrawImage(context, CGRectMake(0, 0, rect.width(), rect.height()), image); CGContextConcatCTM(context, CGAffineTransformInvert(af)); ret = true; } return ret; }
bool GiCanvasIos::drawCachedBitmap(float x, float y, bool secondBmp) { int index = secondBmp ? 1 : 0; CGImageRef image = m_draw->_cacheserr[index] ? NULL : m_draw->_caches[index]; CGContextRef context = m_draw->getContext(); bool ret = false; if (context && image) { CGRect rect = CGRectMake(x, y, m_draw->width(), m_draw->height()); CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height()); CGContextConcatCTM(context, af); // 图像是朝上的,上下文坐标系朝下,上下颠倒显示 CGInterpolationQuality oldQuality = CGContextGetInterpolationQuality(context); CGContextSetInterpolationQuality(context, kCGInterpolationNone); CGContextDrawImage(context, rect, image); CGContextSetInterpolationQuality(context, oldQuality); CGContextConcatCTM(context, CGAffineTransformInvert(af)); // 恢复成坐标系朝下 ret = true; } return ret; }
void EJPath::arcTo(float x1, float y1, float x2, float y2, float radius) { // Lifted from http://code.google.com/p/fxcanvas/ // I have no idea what this code is doing, but it seems to work. // get untransformed currentPos EJVector2 cp = EJVector2ApplyTransform(currentPos, CGAffineTransformInvert(transform)); float a1 = cp.y - y1; float b1 = cp.x - x1; float a2 = y2 - y1; float b2 = x2 - x1; float mm = fabsf(a1 * b2 - b1 * a2); if( mm < 1.0e-8 || radius == 0 ) { lineTo(x1, y1); } else { float dd = a1 * a1 + b1 * b1; float cc = a2 * a2 + b2 * b2; float tt = a1 * a2 + b1 * b2; float k1 = radius * sqrtf(dd) / mm; float k2 = radius * sqrtf(cc) / mm; float j1 = k1 * tt / dd; float j2 = k2 * tt / cc; float cx = k1 * b2 + k2 * b1; float cy = k1 * a2 + k2 * a1; float px = b1 * (k2 + j1); float py = a1 * (k2 + j1); float qx = b2 * (k1 + j2); float qy = a2 * (k1 + j2); float startAngle = atan2f(py - cy, px - cx); float endAngle = atan2f(qy - cy, qx - cx); arc(cx + x1, cy + y1, radius, startAngle, endAngle, (b1 * a2 > b2 * a1)); } }
AffineTransform AffineTransform::invert() const { if (isInvertible()) return AffineTransform(CGAffineTransformInvert(m_transform)); return AffineTransform(); }
Transform2D Transform2D::invert() const { return CGAffineTransformInvert(*this); }
static cairo_int_status_t _cairo_quartz_init_glyph_metrics (cairo_quartz_scaled_font_t *font, cairo_scaled_glyph_t *scaled_glyph) { cairo_int_status_t status = CAIRO_STATUS_SUCCESS; cairo_quartz_font_face_t *font_face = _cairo_quartz_scaled_to_face(font); cairo_text_extents_t extents = {0, 0, 0, 0, 0, 0}; CGGlyph glyph = _cairo_quartz_scaled_glyph_index (scaled_glyph); int advance; CGRect bbox; double emscale = CGFontGetUnitsPerEmPtr (font_face->cgFont); double xmin, ymin, xmax, ymax; if (glyph == INVALID_GLYPH) goto FAIL; if (!CGFontGetGlyphAdvancesPtr (font_face->cgFont, &glyph, 1, &advance) || !CGFontGetGlyphBBoxesPtr (font_face->cgFont, &glyph, 1, &bbox)) goto FAIL; /* broken fonts like Al Bayan return incorrect bounds for some null characters, see https://bugzilla.mozilla.org/show_bug.cgi?id=534260 */ if (unlikely (bbox.origin.x == -32767 && bbox.origin.y == -32767 && bbox.size.width == 65534 && bbox.size.height == 65534)) { bbox.origin.x = bbox.origin.y = 0; bbox.size.width = bbox.size.height = 0; } bbox = CGRectMake (bbox.origin.x / emscale, bbox.origin.y / emscale, bbox.size.width / emscale, bbox.size.height / emscale); /* Should we want to always integer-align glyph extents, we can do so in this way */ #if 0 { CGAffineTransform textMatrix; textMatrix = CGAffineTransformMake (font->base.scale.xx, -font->base.scale.yx, -font->base.scale.xy, font->base.scale.yy, 0.0f, 0.0f); bbox = CGRectApplyAffineTransform (bbox, textMatrix); bbox = CGRectIntegral (bbox); bbox = CGRectApplyAffineTransform (bbox, CGAffineTransformInvert (textMatrix)); } #endif #if 0 fprintf (stderr, "[0x%04x] bbox: %f %f %f %f\n", glyph, bbox.origin.x / emscale, bbox.origin.y / emscale, bbox.size.width / emscale, bbox.size.height / emscale); #endif xmin = CGRectGetMinX(bbox); ymin = CGRectGetMinY(bbox); xmax = CGRectGetMaxX(bbox); ymax = CGRectGetMaxY(bbox); extents.x_bearing = xmin; extents.y_bearing = - ymax; extents.width = xmax - xmin; extents.height = ymax - ymin; extents.x_advance = (double) advance / emscale; extents.y_advance = 0.0; #if 0 fprintf (stderr, "[0x%04x] extents: bearings: %f %f dim: %f %f adv: %f\n\n", glyph, extents.x_bearing, extents.y_bearing, extents.width, extents.height, extents.x_advance); #endif FAIL: _cairo_scaled_glyph_set_metrics (scaled_glyph, &font->base, &extents); return status; }
static cairo_int_status_t _cairo_quartz_init_glyph_metrics (cairo_quartz_scaled_font_t *font, cairo_scaled_glyph_t *scaled_glyph) { cairo_int_status_t status = CAIRO_STATUS_SUCCESS; cairo_quartz_font_face_t *font_face = _cairo_quartz_scaled_to_face(font); cairo_text_extents_t extents = {0, 0, 0, 0, 0, 0}; CGAffineTransform textMatrix; CGGlyph glyph = _cairo_quartz_scaled_glyph_index (scaled_glyph); int advance; CGRect bbox; double emscale = CGFontGetUnitsPerEmPtr (font_face->cgFont); double xscale, yscale; double xmin, ymin, xmax, ymax; if (glyph == INVALID_GLYPH) goto FAIL; if (!CGFontGetGlyphAdvancesPtr (font_face->cgFont, &glyph, 1, &advance) || !CGFontGetGlyphBBoxesPtr (font_face->cgFont, &glyph, 1, &bbox)) goto FAIL; status = _cairo_matrix_compute_basis_scale_factors (&font->base.scale, &xscale, &yscale, 1); if (status) goto FAIL; bbox = CGRectMake (bbox.origin.x / emscale, bbox.origin.y / emscale, bbox.size.width / emscale, bbox.size.height / emscale); /* Should we want to always integer-align glyph extents, we can do so in this way */ #if 0 { CGAffineTransform textMatrix; textMatrix = CGAffineTransformMake (font->base.scale.xx, -font->base.scale.yx, -font->base.scale.xy, font->base.scale.yy, 0.0f, 0.0f); bbox = CGRectApplyAffineTransform (bbox, textMatrix); bbox = CGRectIntegral (bbox); bbox = CGRectApplyAffineTransform (bbox, CGAffineTransformInvert (textMatrix)); } #endif #if 0 fprintf (stderr, "[0x%04x] bbox: %f %f %f %f\n", glyph, bbox.origin.x / emscale, bbox.origin.y / emscale, bbox.size.width / emscale, bbox.size.height / emscale); #endif xmin = CGRectGetMinX(bbox); ymin = CGRectGetMinY(bbox); xmax = CGRectGetMaxX(bbox); ymax = CGRectGetMaxY(bbox); extents.x_bearing = xmin; extents.y_bearing = - ymax; extents.width = xmax - xmin; extents.height = ymax - ymin; extents.x_advance = (double) advance / emscale; extents.y_advance = 0.0; #if 0 fprintf (stderr, "[0x%04x] extents: bearings: %f %f dim: %f %f adv: %f\n\n", glyph, extents.x_bearing, extents.y_bearing, extents.width, extents.height, extents.x_advance); #endif FAIL: _cairo_scaled_glyph_set_metrics (scaled_glyph, &font->base, &extents); return status; }
FX_BOOL CFX_QuartzDeviceDriver::CG_DrawGlypRun(int nChars, const FXTEXT_CHARPOS* pCharPos, CFX_Font* pFont, CFX_FontCache* pCache, const CFX_AffineMatrix* pGlyphMatrix, const CFX_AffineMatrix* pObject2Device, FX_FLOAT font_size, FX_DWORD argb, int alpha_flag, void* pIccTransform) { if (nChars == 0) { return TRUE; } CQuartz2D& quartz2d = ((CApplePlatform *) CFX_GEModule::Get()->GetPlatformData())->_quartz2d; if (!pFont->m_pPlatformFont) { if (pFont->GetPsName() == CFX_WideString::FromLocal("DFHeiStd-W5")) { return FALSE; } pFont->m_pPlatformFont = quartz2d.CreateFont(pFont->m_pFontData, pFont->m_dwSize); if (NULL == pFont->m_pPlatformFont) { return FALSE; } } CFX_FixedBufGrow<FX_WORD, 32> glyph_indices(nChars); CFX_FixedBufGrow<CGPoint, 32> glyph_positions(nChars); for (int i = 0; i < nChars; i++ ) { glyph_indices[i] = pCharPos[i].m_ExtGID; glyph_positions[i].x = pCharPos[i].m_OriginX; glyph_positions[i].y = pCharPos[i].m_OriginY; } CFX_AffineMatrix text_matrix; if (pObject2Device) { text_matrix.Concat(*pObject2Device); } CGAffineTransform matrix_cg = CGAffineTransformMake(text_matrix.a, text_matrix.b, text_matrix.c, text_matrix.d, text_matrix.e, text_matrix.f); matrix_cg = CGAffineTransformConcat(matrix_cg, _foxitDevice2User); CGContextSetTextMatrix(_context, matrix_cg); CGContextSetFont(_context, (CGFontRef)pFont->m_pPlatformFont); CGContextSetFontSize(_context, FXSYS_fabs(font_size)); FX_INT32 a, r, g, b; ArgbDecode(argb, a, r, g, b); CGContextSetRGBFillColor(_context, r / 255.f, g / 255.f, b / 255.f, a / 255.f); SaveState(); if (pGlyphMatrix) { CGPoint origin = CGPointMake( glyph_positions[0].x, glyph_positions[0].y); origin = CGPointApplyAffineTransform(origin, matrix_cg); CGContextTranslateCTM(_context, origin.x, origin.y); CGAffineTransform glyph_matrix = CGAffineTransformMake(pGlyphMatrix->a, pGlyphMatrix->b, pGlyphMatrix->c, pGlyphMatrix->d, pGlyphMatrix->e, pGlyphMatrix->f); if (_foxitDevice2User.d < 0) { glyph_matrix = CGAffineTransformInvert(glyph_matrix); } CGContextConcatCTM(_context, glyph_matrix); CGContextTranslateCTM(_context, -origin.x, -origin.y); } CGContextShowGlyphsAtPositions(_context, (CGGlyph*)glyph_indices, glyph_positions, nChars); RestoreState(FALSE); return TRUE; }
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; } } }