HRESULT D3DTR_DrawGlyphList(D3DContext *d3dc, D3DSDOps *dstOps, jint totalGlyphs, jboolean usePositions, jboolean subPixPos, jboolean rgbOrder, jint lcdContrast, jfloat glyphListOrigX, jfloat glyphListOrigY, unsigned char *images, unsigned char *positions) { int glyphCounter; HRESULT res = S_OK; J2dTraceLn(J2D_TRACE_INFO, "D3DTR_DrawGlyphList"); RETURN_STATUS_IF_NULL(d3dc, E_FAIL); RETURN_STATUS_IF_NULL(d3dc->Get3DDevice(), E_FAIL); RETURN_STATUS_IF_NULL(dstOps, E_FAIL); RETURN_STATUS_IF_NULL(images, E_FAIL); if (usePositions) { RETURN_STATUS_IF_NULL(positions, E_FAIL); } glyphMode = MODE_NOT_INITED; isCachedDestValid = JNI_FALSE; for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) { jint x, y; jfloat glyphx, glyphy; jboolean grayscale; GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images)); if (ginfo == NULL) { // this shouldn't happen, but if it does we'll just break out... J2dRlsTraceLn(J2D_TRACE_ERROR, "D3DTR_DrawGlyphList: glyph info is null"); break; } grayscale = (ginfo->rowBytes == ginfo->width); if (usePositions) { jfloat posx = NEXT_FLOAT(positions); jfloat posy = NEXT_FLOAT(positions); glyphx = glyphListOrigX + posx + ginfo->topLeftX; glyphy = glyphListOrigY + posy + ginfo->topLeftY; FLOOR_ASSIGN(x, glyphx); FLOOR_ASSIGN(y, glyphy); } else { glyphx = glyphListOrigX + ginfo->topLeftX; glyphy = glyphListOrigY + ginfo->topLeftY; FLOOR_ASSIGN(x, glyphx); FLOOR_ASSIGN(y, glyphy); glyphListOrigX += ginfo->advanceX; glyphListOrigY += ginfo->advanceY; } if (ginfo->image == NULL) { continue; } if (grayscale) { // grayscale or monochrome glyph data if (ginfo->width <= D3DTR_CACHE_CELL_WIDTH && ginfo->height <= D3DTR_CACHE_CELL_HEIGHT && SUCCEEDED(d3dc->InitGrayscaleGlyphCache())) { res = D3DTR_DrawGrayscaleGlyphViaCache(d3dc, ginfo, x, y); } else { res = D3DTR_DrawGrayscaleGlyphNoCache(d3dc, ginfo, x, y); } } else { // LCD-optimized glyph data jint rowBytesOffset = 0; if (subPixPos) { jint frac = (jint)((glyphx - x) * 3); if (frac != 0) { rowBytesOffset = 3 - frac; x += 1; } } if (rowBytesOffset == 0 && ginfo->width <= D3DTR_CACHE_CELL_WIDTH && ginfo->height <= D3DTR_CACHE_CELL_HEIGHT && SUCCEEDED(d3dc->InitLCDGlyphCache())) { res = D3DTR_DrawLCDGlyphViaCache(d3dc, dstOps, ginfo, x, y, glyphCounter, totalGlyphs, rgbOrder, lcdContrast); } else { res = D3DTR_DrawLCDGlyphNoCache(d3dc, dstOps, ginfo, x, y, rowBytesOffset, rgbOrder, lcdContrast); } } if (FAILED(res)) { break; } } D3DTR_DisableGlyphModeState(d3dc); return res; }
GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) { int g, bytesNeeded; jlong *imagePtrs; jfloat* positions = NULL; GlyphInfo *ginfo; GlyphBlitVector *gbv; jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX); jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY); jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen); jlongArray glyphImages = (jlongArray) (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages); jfloatArray glyphPositions = (*env)->GetBooleanField(env, glyphlist, sunFontIDs.glyphListUsePos) ? (jfloatArray) (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphListPos) : NULL; bytesNeeded = sizeof(GlyphBlitVector)+sizeof(ImageRef)*len; gbv = (GlyphBlitVector*)malloc(bytesNeeded); gbv->numGlyphs = len; gbv->glyphs = (ImageRef*)((unsigned char*)gbv+sizeof(GlyphBlitVector)); imagePtrs = (*env)->GetPrimitiveArrayCritical(env, glyphImages, NULL); if (imagePtrs == NULL) { free(gbv); return (GlyphBlitVector*)NULL; } /* Add 0.5 to x and y and then use floor (or an equivalent operation) * to round down the glyph positions to integral pixel positions. */ x += 0.5f; y += 0.5f; if (glyphPositions) { int n = -1; positions = (*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL); if (positions == NULL) { (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, JNI_ABORT); free(gbv); return (GlyphBlitVector*)NULL; } for (g=0; g<len; g++) { jfloat px = x + positions[++n]; jfloat py = y + positions[++n]; ginfo = (GlyphInfo*)imagePtrs[g]; gbv->glyphs[g].glyphInfo = ginfo; gbv->glyphs[g].pixels = ginfo->image; gbv->glyphs[g].width = ginfo->width; gbv->glyphs[g].rowBytes = ginfo->rowBytes; gbv->glyphs[g].height = ginfo->height; FLOOR_ASSIGN(gbv->glyphs[g].x, px + ginfo->topLeftX); FLOOR_ASSIGN(gbv->glyphs[g].y, py + ginfo->topLeftY); } (*env)->ReleasePrimitiveArrayCritical(env,glyphPositions, positions, JNI_ABORT); } else { for (g=0; g<len; g++) { ginfo = (GlyphInfo*)imagePtrs[g]; gbv->glyphs[g].glyphInfo = ginfo; gbv->glyphs[g].pixels = ginfo->image; gbv->glyphs[g].width = ginfo->width; gbv->glyphs[g].rowBytes = ginfo->rowBytes; gbv->glyphs[g].height = ginfo->height; FLOOR_ASSIGN(gbv->glyphs[g].x, x + ginfo->topLeftX); FLOOR_ASSIGN(gbv->glyphs[g].y, y + ginfo->topLeftY); /* copy image data into this array at x/y locations */ x += ginfo->advanceX; y += ginfo->advanceY; } } (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, JNI_ABORT); return gbv; }
void OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps, jint totalGlyphs, jboolean usePositions, jboolean subPixPos, jboolean rgbOrder, jint lcdContrast, jfloat glyphListOrigX, jfloat glyphListOrigY, unsigned char *images, unsigned char *positions) { int glyphCounter; J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList"); RETURN_IF_NULL(oglc); RETURN_IF_NULL(dstOps); RETURN_IF_NULL(images); if (usePositions) { RETURN_IF_NULL(positions); } glyphMode = MODE_NOT_INITED; isCachedDestValid = JNI_FALSE; for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) { jint x, y; jfloat glyphx, glyphy; jboolean grayscale, ok; GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images)); if (ginfo == NULL) { // this shouldn't happen, but if it does we'll just break out... J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLTR_DrawGlyphList: glyph info is null"); break; } grayscale = (ginfo->rowBytes == ginfo->width); if (usePositions) { jfloat posx = NEXT_FLOAT(positions); jfloat posy = NEXT_FLOAT(positions); glyphx = glyphListOrigX + posx + ginfo->topLeftX; glyphy = glyphListOrigY + posy + ginfo->topLeftY; FLOOR_ASSIGN(x, glyphx); FLOOR_ASSIGN(y, glyphy); } else { glyphx = glyphListOrigX + ginfo->topLeftX; glyphy = glyphListOrigY + ginfo->topLeftY; FLOOR_ASSIGN(x, glyphx); FLOOR_ASSIGN(y, glyphy); glyphListOrigX += ginfo->advanceX; glyphListOrigY += ginfo->advanceY; } if (ginfo->image == NULL) { continue; } if (grayscale) { // grayscale or monochrome glyph data if (cacheStatus != CACHE_LCD && ginfo->width <= OGLTR_CACHE_CELL_WIDTH && ginfo->height <= OGLTR_CACHE_CELL_HEIGHT) { ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y); } else { ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y); } } else { // LCD-optimized glyph data jint rowBytesOffset = 0; if (subPixPos) { jint frac = (jint)((glyphx - x) * 3); if (frac != 0) { rowBytesOffset = 3 - frac; x += 1; } } if (rowBytesOffset == 0 && cacheStatus != CACHE_GRAY && ginfo->width <= OGLTR_CACHE_CELL_WIDTH && ginfo->height <= OGLTR_CACHE_CELL_HEIGHT) { ok = OGLTR_DrawLCDGlyphViaCache(oglc, dstOps, ginfo, x, y, glyphCounter, totalGlyphs, rgbOrder, lcdContrast); } else { ok = OGLTR_DrawLCDGlyphNoCache(oglc, dstOps, ginfo, x, y, rowBytesOffset, rgbOrder, lcdContrast); } } if (!ok) { break; } } OGLTR_DisableGlyphModeState(); }
/* * LCD text utilises a filter which spreads energy to adjacent subpixels. * So we add 3 bytes (one whole pixel) of padding at the start of every row * to hold energy from the very leftmost sub-pixel. * This is to the left of the intended glyph image position so LCD text also * adjusts the top-left X position of the padded image one pixel to the left * so a glyph image is drawn in the same place it would be if the padding * were not present. * * So in the glyph cache for LCD text the first two bytes of every row are * zero. * We make use of this to be able to adjust the rendering position of the * text when the client specifies a fractional metrics sub-pixel positioning * rendering hint. * * So the first 6 bytes in a cache row looks like : * 00 00 Ex G0 G1 G2 * * where * 00 are the always zero bytes * Ex is extra energy spread from the glyph into the left padding pixel. * Gn are the RGB component bytes of the first pixel of the glyph image * For an RGB display G0 is the red component, etc. * * If a glyph is drawn at X=12 then the G0 G1 G2 pixel is placed at that * position : ie G0 is drawn in the first sub-pixel at X=12 * * Draw at X=12,0 * PIXEL POS 11 11 11 12 12 12 13 13 13 * SUBPX POS 0 1 2 0 1 2 0 1 2 * 00 00 Ex G0 G1 G2 * * If a sub-pixel rounded glyph position is calculated as being X=12.33 - * ie 12 and one-third pixels, we want the result to look like this : * Draw at X=12,1 * PIXEL POS 11 11 11 12 12 12 13 13 13 * SUBPX POS 0 1 2 0 1 2 0 1 2 * 00 00 Ex G0 G1 G2 * * ie the G0 byte is moved one sub-pixel to the right. * To do this we need to make two adjustments : * - set X=X+1 * - set start of scan row to start+2, ie index past the two zero bytes * ie we don't need the 00 00 bytes at all any more. Rendering start X * can skip over those. * * Lets look at the final case : * If a sub-pixel rounded glyph position is calculated as being X=12.67 - * ie 12 and two-third pixels, we want the result to look like this : * Draw at X=12,2 * PIXEL POS 11 11 11 12 12 12 13 13 13 * SUBPX POS 0 1 2 0 1 2 0 1 2 * 00 00 Ex G0 G1 G2 * * ie the G0 byte is moved two sub-pixels to the right, so that the image * starts at 12.67 * To do this we need to make these two adjustments : * - set X=X+1 * - set start of scan row to start+1, ie index past the first zero byte * In this case the second of the 00 bytes is used as a no-op on the first * red sub-pixel position. * * The final adjustment needed to make all this work is note that if * we moved the start of row one or two bytes in we will go one or two bytes * past the end of the row. So the glyph cache needs to have 2 bytes of * zero padding at the end of each row. This is the extra memory cost to * accommodate this algorithm. * * The resulting text is perhaps fractionally better in overall perception * than rounding to the whole pixel grid, as a few issues arise. * * * the improvement in inter-glyph spacing as well as being limited * to 1/3 pixel resolution, is also limited because the glyphs were hinted * so they fit to the whole pixel grid. It may be worthwhile to pursue * disabling x-axis gridfitting. * * * an LCD display may have gaps between the pixels that are greater * than the subpixels. Thus for thin stemmed fonts, if the shift causes * the "heart" of a stem to span whole pixels it may appear more diffuse - * less sharp. Eliminating hinting would probably not make this worse - in * effect we have already doing that here. But it would improve the spacing. * * * perhaps contradicting the above point in some ways, more diffuse glyphs * are better at reducing colour fringing, but what appears to be more * colour fringing in this FM case is more likely attributable to a greater * likelihood for glyphs to abutt. In integer metrics or even whole pixel * rendered fractional metrics, there's typically more space between the * glyphs. Perhaps disabling X-axis grid-fitting will help with that. */ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) { int g, bytesNeeded; jlong *imagePtrs; jfloat* positions = NULL; GlyphInfo *ginfo; GlyphBlitVector *gbv; jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX); jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY); jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen); jlongArray glyphImages = (jlongArray) (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages); jfloatArray glyphPositions = (*env)->GetBooleanField(env, glyphlist, sunFontIDs.glyphListUsePos) ? (jfloatArray) (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphListPos) : NULL; jboolean subPixPos = (*env)->GetBooleanField(env,glyphlist, sunFontIDs.lcdSubPixPos); bytesNeeded = sizeof(GlyphBlitVector)+sizeof(ImageRef)*len; gbv = (GlyphBlitVector*)malloc(bytesNeeded); gbv->numGlyphs = len; gbv->glyphs = (ImageRef*)((unsigned char*)gbv+sizeof(GlyphBlitVector)); imagePtrs = (*env)->GetPrimitiveArrayCritical(env, glyphImages, NULL); if (imagePtrs == NULL) { free(gbv); return (GlyphBlitVector*)NULL; } /* The position of the start of the text is adjusted up so * that we can round it to an integral pixel position for a * bitmap glyph or non-subpixel positioning, and round it to an * integral subpixel position for that case, hence 0.5/3 = 0.166667 * Presently subPixPos means FM, and FM disables embedded bitmaps * Therefore if subPixPos is true we should never get embedded bitmaps * and the glyphlist will be homogenous. This test and the position * adjustments will need to be per glyph once this case becomes * heterogenous. * Also set subPixPos=false if detect a B&W bitmap as we only * need to test that on a per glyph basis once the list becomes * heterogenous */ if (subPixPos && len > 0) { ginfo = (GlyphInfo*)imagePtrs[0]; /* rowBytes==width tests if its a B&W or LCD glyph */ if (ginfo->width == ginfo->rowBytes) { subPixPos = JNI_FALSE; } } if (subPixPos) { x += 0.1666667f; y += 0.1666667f; } else { x += 0.5f; y += 0.5f; } if (glyphPositions) { int n = -1; positions = (*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL); if (positions == NULL) { (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, JNI_ABORT); free(gbv); return (GlyphBlitVector*)NULL; } for (g=0; g<len; g++) { jfloat px, py; ginfo = (GlyphInfo*)imagePtrs[g]; gbv->glyphs[g].glyphInfo = ginfo; gbv->glyphs[g].pixels = ginfo->image; gbv->glyphs[g].width = ginfo->width; gbv->glyphs[g].rowBytes = ginfo->rowBytes; gbv->glyphs[g].height = ginfo->height; px = x + positions[++n]; py = y + positions[++n]; /* * Subpixel positioning may be requested for LCD text. * * Subpixel positioning can take place only in the direction in * which the subpixels increase the resolution. * So this is useful for the typical case of vertical stripes * increasing the resolution in the direction of the glyph * advances - ie typical horizontally laid out text. * If the subpixel stripes are horizontal, subpixel positioning * can take place only in the vertical direction, which isn't * as useful - you would have to be drawing rotated text on * a display which actually had that organisation. A pretty * unlikely combination. * So this is supported only for vertical stripes which * increase the horizontal resolution. * If in this case the client also rotates the text then there * will still be some benefit for small rotations. For 90 degree * rotation there's no horizontal advance and less benefit * from the subpixel rendering too. * The test for width==rowBytes detects the case where the glyph * is a B&W image obtained from an embedded bitmap. In that * case we cannot apply sub-pixel positioning so ignore it. * This is handled on a per glyph basis. */ if (subPixPos) { int frac; float pos = px + ginfo->topLeftX; FLOOR_ASSIGN(gbv->glyphs[g].x, pos); /* Calculate the fractional pixel position - ie the subpixel * position within the RGB/BGR triple. We are rounding to * the nearest, even though we just do (int) since at the * start of the loop the position was already adjusted by * 0.5 (sub)pixels to get rounding. * Thus the "fractional" position will be 0, 1 or 2. * eg 0->0.32 is 0, 0.33->0.66 is 1, > 0.66->0.99 is 2. * We can use an (int) cast here since the floor operation * above guarantees us that the value is positive. */ frac = (int)((pos - gbv->glyphs[g].x)*3); if (frac == 0) { /* frac rounded down to zero, so this is equivalent * to no sub-pixel positioning. */ gbv->glyphs[g].rowBytesOffset = 0; } else { /* In this case we need to adjust both the position at * which the glyph will be positioned by one pixel to the * left and adjust the position in the glyph image row * from which to extract the data * Every glyph image row has 2 bytes padding * on the right to account for this. */ gbv->glyphs[g].rowBytesOffset = 3-frac; gbv->glyphs[g].x += 1; } } else { FLOOR_ASSIGN(gbv->glyphs[g].x, px + ginfo->topLeftX); gbv->glyphs[g].rowBytesOffset = 0; } FLOOR_ASSIGN(gbv->glyphs[g].y, py + ginfo->topLeftY); } (*env)->ReleasePrimitiveArrayCritical(env,glyphPositions, positions, JNI_ABORT); } else { for (g=0; g<len; g++) { ginfo = (GlyphInfo*)imagePtrs[g]; gbv->glyphs[g].glyphInfo = ginfo; gbv->glyphs[g].pixels = ginfo->image; gbv->glyphs[g].width = ginfo->width; gbv->glyphs[g].rowBytes = ginfo->rowBytes; gbv->glyphs[g].height = ginfo->height; if (subPixPos) { int frac; float pos = x + ginfo->topLeftX; FLOOR_ASSIGN(gbv->glyphs[g].x, pos); frac = (int)((pos - gbv->glyphs[g].x)*3); if (frac == 0) { gbv->glyphs[g].rowBytesOffset = 0; } else { gbv->glyphs[g].rowBytesOffset = 3-frac; gbv->glyphs[g].x += 1; } } else { FLOOR_ASSIGN(gbv->glyphs[g].x, x + ginfo->topLeftX); gbv->glyphs[g].rowBytesOffset = 0; } FLOOR_ASSIGN(gbv->glyphs[g].y, y + ginfo->topLeftY); /* copy image data into this array at x/y locations */ x += ginfo->advanceX; y += ginfo->advanceY; } } (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, JNI_ABORT); return gbv; }