8263583: Emoji rendering on macOS

Reviewed-by: serb, prr
This commit is contained in:
Dmitry Batrak 2021-05-31 07:14:53 +00:00
parent 1ab2776947
commit 236bd89dc3
29 changed files with 907 additions and 143 deletions

@ -41,3 +41,5 @@
+ (NSFont *) nsFontForJavaFont:(jobject)javaFont env:(JNIEnv *)env;
@end
bool IsEmojiFont(CTFontRef font);

@ -583,3 +583,14 @@ JNI_COCOA_ENTER(env);
CFRelease(fds);
JNI_COCOA_EXIT(env);
}
static CFStringRef EMOJI_FONT_NAME = CFSTR("Apple Color Emoji");
bool IsEmojiFont(CTFontRef font)
{
CFStringRef name = CTFontCopyFullName(font);
if (name == NULL) return false;
bool isFixedColor = CFStringCompare(name, EMOJI_FONT_NAME, 0) == kCFCompareEqualTo;
CFRelease(name);
return isFixedColor;
}

@ -151,7 +151,7 @@ JNI_COCOA_ENTER(env);
// to indicate we should use CoreText to substitute the character
CGGlyph glyph;
const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph);
CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
CGGlyphImages_GetGlyphMetrics(fallback, &awtStrike->fAltTx, awtStrike->fStyle, &glyph, 1, NULL, &advance);
CFRelease(fallback);
advance = CGSizeApplyAffineTransform(advance, awtStrike->fFontTx);
if (!JRSFontStyleUsesFractionalMetrics(awtStrike->fStyle)) {
@ -188,7 +188,7 @@ JNI_COCOA_ENTER(env);
const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph);
CGRect bbox;
JRSFontGetBoundingBoxesForGlyphsAndStyle(fallback, &tx, awtStrike->fStyle, &glyph, 1, &bbox);
CGGlyphImages_GetGlyphMetrics(fallback, &tx, awtStrike->fStyle, &glyph, 1, &bbox, NULL);
CFRelease(fallback);
// the origin of this bounding box is relative to the bottom-left corner baseline

@ -33,5 +33,12 @@ void
CGGlyphImages_GetGlyphImagePtrs(jlong glyphInfos[],
const AWTStrike *strike,
jint rawGlyphCodes[], const CFIndex len);
void
CGGlyphImages_GetGlyphMetrics(const CTFontRef font,
const CGAffineTransform *tx,
const JRSFontRenderingStyle style,
const CGGlyph glyphs[],
size_t count,
CGRect bboxes[],
CGSize advances[]);
#endif /* __CGGLYPHIMAGES_H */

@ -309,6 +309,40 @@ CGGI_CopyImageFromCanvasToAlphaInfo(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
}
}
static void
CGGI_CopyImageFromCanvasToARGBInfo(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
{
CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(canvas->context);
bool littleEndian = (bitmapInfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Little;
UInt32 *src = (UInt32 *)canvas->image->data;
size_t srcRowWidth = canvas->image->width;
UInt8 *dest = (UInt8 *)info->image;
size_t destRowWidth = info->width;
size_t height = info->height;
size_t y;
for (y = 0; y < height; y++) {
size_t srcRow = y * srcRowWidth;
if (littleEndian) {
UInt16 destRowBytes = info->rowBytes;
memcpy(dest, src + srcRow, destRowBytes);
dest += destRowBytes;
} else {
size_t x;
for (x = 0; x < destRowWidth; x++) {
UInt32 p = src[srcRow + x];
*dest++ = (p >> 24 & 0xFF); // blue (alpha-premultiplied)
*dest++ = (p >> 16 & 0xFF); // green (alpha-premultiplied)
*dest++ = (p >> 8 & 0xFF); // red (alpha-premultiplied)
*dest++ = (p & 0xFF); // alpha
}
}
}
}
#pragma mark --- Pixel Size, Modes, and Canvas Shaping Helper Functions ---
@ -326,6 +360,14 @@ static CGGI_GlyphInfoDescriptor grey =
{ 1, &CGGI_CopyImageFromCanvasToAlphaInfo };
static CGGI_GlyphInfoDescriptor rgb =
{ 3, &CGGI_CopyImageFromCanvasToRGBInfo };
static CGGI_GlyphInfoDescriptor argb =
{ 4, &CGGI_CopyImageFromCanvasToARGBInfo };
static inline CGGI_GlyphInfoDescriptor*
CGGI_GetGlyphInfoDescriptor(const CGGI_RenderingMode *mode, CTFontRef font)
{
return IsEmojiFont(font) ? &argb : mode->glyphDescriptor;
}
static inline CGGI_RenderingMode
CGGI_GetRenderingMode(const AWTStrike *strike)
@ -459,11 +501,11 @@ CGGI_SizeCanvas(CGGI_GlyphCanvas *canvas, const vImagePixelCount width,
}
/*
* Clear the canvas by blitting white only into the region of interest
* Clear the canvas by blitting white (or transparent background for color glyphs) only into the region of interest
* (the rect which we will copy out of once the glyph is struck).
*/
static inline void
CGGI_ClearCanvas(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
CGGI_ClearCanvas(CGGI_GlyphCanvas *canvas, GlyphInfo *info, bool transparent)
{
vImage_Buffer canvasRectToClear;
canvasRectToClear.data = canvas->image->data;
@ -474,13 +516,16 @@ CGGI_ClearCanvas(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
// clean the canvas
#ifdef CGGI_DEBUG
Pixel_8888 opaqueWhite = { 0xE0, 0xE0, 0xE0, 0xE0 };
Pixel_8888 background = { 0xE0, 0xE0, 0xE0, 0xE0 };
#else
Pixel_8888 opaqueWhite = { 0xFF, 0xFF, 0xFF, 0xFF };
Pixel_8888 background = { transparent ? 0 : 0xFF,
transparent ? 0 : 0xFF,
transparent ? 0 : 0xFF,
transparent ? 0 : 0xFF };
#endif
// clear canvas background and set foreground color
vImageBufferFill_ARGB8888(&canvasRectToClear, opaqueWhite, kvImageNoFlags);
// clear canvas background
vImageBufferFill_ARGB8888(&canvasRectToClear, background, kvImageNoFlags);
}
@ -493,9 +538,9 @@ CGGI_ClearCanvas(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
static inline GlyphInfo *
CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox,
const AWTStrike *strike,
const CGGI_RenderingMode *mode)
const CGGI_GlyphInfoDescriptor *glyphDescriptor)
{
size_t pixelSize = mode->glyphDescriptor->pixelSize;
size_t pixelSize = glyphDescriptor->pixelSize;
// adjust the bounding box to be 1px bigger on each side than what
// CGFont-whatever suggests - because it gives a bounding box that
@ -560,7 +605,7 @@ CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox,
static inline void
CGGI_CreateImageForGlyph
(CGGI_GlyphCanvas *canvas, const CGGlyph glyph,
GlyphInfo *info, const CGGI_RenderingMode *mode)
GlyphInfo *info, const CGGI_GlyphInfoDescriptor *glyphDescriptor, const AWTStrike *strike, CTFontRef font)
{
if (isnan(info->topLeftX) || isnan(info->topLeftY)) {
// Explicitly set glyphInfo width/height to be 0 to ensure
@ -569,20 +614,48 @@ CGGI_CreateImageForGlyph
info->height = 0;
// copy the "empty" glyph from the canvas into the info
(*mode->glyphDescriptor->copyFxnPtr)(canvas, info);
(*glyphDescriptor->copyFxnPtr)(canvas, info);
return;
}
// clean the canvas
CGGI_ClearCanvas(canvas, info);
CGGI_ClearCanvas(canvas, info, glyphDescriptor == &argb);
// strike the glyph in the upper right corner
CGContextShowGlyphsAtPoint(canvas->context,
-info->topLeftX,
canvas->image->height + info->topLeftY,
&glyph, 1);
CGFloat x = -info->topLeftX;
CGFloat y = canvas->image->height + info->topLeftY;
if (glyphDescriptor == &argb) {
// Emoji glyphs are not rendered by CGContextShowGlyphsAtPoint.
// Also, it's not possible to use transformation matrix to get the emoji glyph
// rendered for the desired font size - actual-size font object is needed.
// The logic here must match the logic in CGGlyphImages_GetGlyphMetrics,
// which calculates glyph metrics.
CGAffineTransform matrix = CGContextGetTextMatrix(canvas->context);
CGFloat fontSize = sqrt(fabs(matrix.a * matrix.d - matrix.b * matrix.c));
CTFontRef sizedFont = CTFontCreateCopyWithSymbolicTraits(font, fontSize, NULL, 0, 0);
CGFloat normFactor = 1.0 / fontSize;
CGAffineTransform normalizedMatrix = CGAffineTransformScale(matrix, normFactor, normFactor);
CGContextSetTextMatrix(canvas->context, normalizedMatrix);
CGPoint userPoint = CGPointMake(x, y);
CGAffineTransform normalizedMatrixInv = CGAffineTransformInvert(normalizedMatrix);
CGPoint textPoint = CGPointApplyAffineTransform(userPoint, normalizedMatrixInv);
CTFontDrawGlyphs(sizedFont, &glyph, &textPoint, 1, canvas->context);
CFRelease(sizedFont);
// restore context's original state
CGContextSetTextMatrix(canvas->context, matrix);
CGContextSetFontSize(canvas->context, 1); // CTFontDrawGlyphs tampers with it
} else {
CGContextShowGlyphsAtPoint(canvas->context, x, y, &glyph, 1);
}
// copy the glyph from the canvas into the info
(*mode->glyphDescriptor->copyFxnPtr)(canvas, info);
(*glyphDescriptor->copyFxnPtr)(canvas, info);
}
/*
@ -615,13 +688,13 @@ CGGI_CreateImageForUnicode
JRSFontRenderingStyle style = JRSFontAlignStyleForFractionalMeasurement(strike->fStyle);
CGRect bbox;
JRSFontGetBoundingBoxesForGlyphsAndStyle(fallback, &tx, style, &glyph, 1, &bbox);
CGSize advance;
CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
CGGlyphImages_GetGlyphMetrics(fallback, &tx, style, &glyph, 1, &bbox, &advance);
CGGI_GlyphInfoDescriptor *glyphDescriptor = CGGI_GetGlyphInfoDescriptor(mode, fallback);
// create the Sun2D GlyphInfo we are going to strike into
GlyphInfo *info = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, mode);
GlyphInfo *info = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, glyphDescriptor);
// fix the context size, just in case the substituted character is unexpectedly large
CGGI_SizeCanvas(canvas, info->width, info->height, mode);
@ -634,7 +707,7 @@ CGGI_CreateImageForUnicode
CFRelease(cgFallback);
// clean the canvas - align, strike, and copy the glyph from the canvas into the info
CGGI_CreateImageForGlyph(canvas, glyph, info, mode);
CGGI_CreateImageForGlyph(canvas, glyph, info, glyphDescriptor, strike, fallback);
// restore the state of the world
CGContextRestoreGState(canvas->context);
@ -677,11 +750,14 @@ CGGI_FillImagesForGlyphsWithSizedCanvas(CGGI_GlyphCanvas *canvas,
CGContextSetFont(canvas->context, strike->fAWTFont->fNativeCGFont);
JRSFontSetRenderingStyleOnContext(canvas->context, strike->fStyle);
CTFontRef mainFont = (CTFontRef)strike->fAWTFont->fFont;
CGGI_GlyphInfoDescriptor* mainFontDescriptor = CGGI_GetGlyphInfoDescriptor(mode, mainFont);
CFIndex i;
for (i = 0; i < len; i++) {
GlyphInfo *info = (GlyphInfo *)jlong_to_ptr(glyphInfos[i]);
if (info != NULL) {
CGGI_CreateImageForGlyph(canvas, glyphs[i], info, mode);
CGGI_CreateImageForGlyph(canvas, glyphs[i], info, mainFontDescriptor, strike, mainFont);
} else {
info = CGGI_CreateImageForUnicode(canvas, strike, mode, uniChars[i]);
glyphInfos[i] = ptr_to_jlong(info);
@ -774,8 +850,9 @@ CGGI_CreateGlyphInfos(jlong *glyphInfos, const AWTStrike *strike,
CGAffineTransform tx = strike->fTx;
JRSFontRenderingStyle bboxCGMode = JRSFontAlignStyleForFractionalMeasurement(strike->fStyle);
JRSFontGetBoundingBoxesForGlyphsAndStyle((CTFontRef)font->fFont, &tx, bboxCGMode, glyphs, len, bboxes);
CTFontGetAdvancesForGlyphs((CTFontRef)font->fFont, kCTFontDefaultOrientation, glyphs, advances, len);
CTFontRef fontRef = (CTFontRef)font->fFont;
CGGlyphImages_GetGlyphMetrics(fontRef, &tx, bboxCGMode, glyphs, len, bboxes, advances);
CGGI_GlyphInfoDescriptor* mainFontDescriptor = CGGI_GetGlyphInfoDescriptor(mode, fontRef);
size_t maxWidth = 1;
size_t maxHeight = 1;
@ -792,7 +869,7 @@ CGGI_CreateGlyphInfos(jlong *glyphInfos, const AWTStrike *strike,
CGSize advance = advances[i];
CGRect bbox = bboxes[i];
GlyphInfo *glyphInfo = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, mode);
GlyphInfo *glyphInfo = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, mainFontDescriptor);
if (maxWidth < glyphInfo->width) maxWidth = glyphInfo->width;
if (maxHeight < glyphInfo->height) maxHeight = glyphInfo->height;
@ -888,3 +965,59 @@ CGGlyphImages_GetGlyphImagePtrs(jlong glyphInfos[],
free(buffer);
}
/*
* Calculates bounding boxes (for given transform) and advance (for untransformed 1pt-size font) for specified glyphs.
*/
void
CGGlyphImages_GetGlyphMetrics(const CTFontRef font,
const CGAffineTransform *tx,
const JRSFontRenderingStyle style,
const CGGlyph glyphs[],
size_t count,
CGRect bboxes[],
CGSize advances[]) {
if (IsEmojiFont(font)) {
// Glyph metrics for emoji font are not strictly proportional to font size,
// so we need to construct real-sized font object to calculate them.
// The logic here must match the logic in CGGI_CreateImageForGlyph,
// which performs glyph drawing.
CGFloat fontSize = sqrt(fabs(tx->a * tx->d - tx->b * tx->c));
CTFontRef sizedFont = CTFontCreateCopyWithSymbolicTraits(font, fontSize, NULL, 0, 0);
if (bboxes) {
// JRSFontGetBoundingBoxesForGlyphsAndStyle works incorrectly for AppleColorEmoji font:
// it uses bottom left corner of the glyph's bounding box as a fixed point of transformation
// instead of glyph's origin point (used at drawing). So, as a workaround,
// we request a bounding box for the untransformed glyph, and apply the transform ourselves.
JRSFontGetBoundingBoxesForGlyphsAndStyle(sizedFont, &CGAffineTransformIdentity, style, glyphs, count, bboxes);
CGAffineTransform txNormalized = CGAffineTransformMake(tx->a / fontSize,
tx->b / fontSize,
tx->c / fontSize,
tx->d / fontSize,
0, 0);
for (int i = 0; i < count; i++) {
bboxes[i] = CGRectApplyAffineTransform(bboxes[i], txNormalized);
}
}
if (advances) {
CTFontGetAdvancesForGlyphs(sizedFont, kCTFontDefaultOrientation, glyphs, advances, count);
for (int i = 0; i < count; i++) {
// Calling code will scale the result back
advances[i].width /= fontSize;
advances[i].height /= fontSize;
}
}
CFRelease(sizedFont);
} else {
if (bboxes) {
JRSFontGetBoundingBoxesForGlyphsAndStyle(font, tx, style, glyphs, count, bboxes);
}
if (advances) {
CTFontGetAdvancesForGlyphs(font, kCTFontDefaultOrientation, glyphs, advances, count);
}
}
}

@ -324,6 +324,19 @@ void MTLTR_FreeGlyphCaches() {
}
}
static MTLPaint* storedPaint = nil;
static void EnableColorGlyphPainting(MTLContext *mtlc) {
storedPaint = mtlc.paint;
mtlc.paint = [[MTLPaint alloc] init];
}
static void DisableColorGlyphPainting(MTLContext *mtlc) {
[mtlc.paint release];
mtlc.paint = storedPaint;
storedPaint = nil;
}
static jboolean
MTLTR_DrawGrayscaleGlyphViaCache(MTLContext *mtlc,
GlyphInfo *ginfo, jint x, jint y, BMTLSDOps *dstOps)
@ -337,6 +350,8 @@ MTLTR_DrawGrayscaleGlyphViaCache(MTLContext *mtlc,
} else if (glyphMode == MODE_USE_CACHE_LCD) {
[mtlc.encoderManager endEncoder];
lcdCacheEncoder = nil;
} else if (glyphMode == MODE_NO_CACHE_COLOR) {
DisableColorGlyphPainting(mtlc);
}
MTLTR_EnableGlyphVertexCache(mtlc, dstOps);
glyphMode = MODE_USE_CACHE_GRAY;
@ -383,6 +398,8 @@ MTLTR_DrawLCDGlyphViaCache(MTLContext *mtlc, BMTLSDOps *dstOps,
MTLVertexCache_DisableMaskCache(mtlc);
} else if (glyphMode == MODE_USE_CACHE_GRAY) {
MTLTR_DisableGlyphVertexCache(mtlc);
} else if (glyphMode == MODE_NO_CACHE_COLOR) {
DisableColorGlyphPainting(mtlc);
}
if (glyphCacheLCD == NULL) {
@ -455,6 +472,8 @@ MTLTR_DrawGrayscaleGlyphNoCache(MTLContext *mtlc,
} else if (glyphMode == MODE_USE_CACHE_LCD) {
[mtlc.encoderManager endEncoder];
lcdCacheEncoder = nil;
} else if (glyphMode == MODE_NO_CACHE_COLOR) {
DisableColorGlyphPainting(mtlc);
}
MTLVertexCache_EnableMaskCache(mtlc, dstOps);
glyphMode = MODE_NO_CACHE_GRAY;
@ -517,6 +536,8 @@ MTLTR_DrawLCDGlyphNoCache(MTLContext *mtlc, BMTLSDOps *dstOps,
} else if (glyphMode == MODE_USE_CACHE_LCD) {
[mtlc.encoderManager endEncoder];
lcdCacheEncoder = nil;
} else if (glyphMode == MODE_NO_CACHE_COLOR) {
DisableColorGlyphPainting(mtlc);
}
if (blitTexture == nil) {
@ -584,6 +605,51 @@ MTLTR_DrawLCDGlyphNoCache(MTLContext *mtlc, BMTLSDOps *dstOps,
return JNI_TRUE;
}
static jboolean
MTLTR_DrawColorGlyphNoCache(MTLContext *mtlc,
GlyphInfo *ginfo, jint x, jint y, BMTLSDOps *dstOps)
{
id<MTLTexture> dest = dstOps->pTexture;
const void *src = ginfo->image;
jint w = ginfo->width;
jint h = ginfo->height;
jint rowBytes = ginfo->rowBytes;
unsigned int imageSize = rowBytes * h;
J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawColorGlyphNoCache");
if (glyphMode != MODE_NO_CACHE_COLOR) {
if (glyphMode == MODE_NO_CACHE_GRAY) {
MTLVertexCache_DisableMaskCache(mtlc);
} else if (glyphMode == MODE_USE_CACHE_GRAY) {
MTLTR_DisableGlyphVertexCache(mtlc);
} else if (glyphMode == MODE_USE_CACHE_LCD) {
[mtlc.encoderManager endEncoder];
lcdCacheEncoder = nil;
}
glyphMode = MODE_NO_CACHE_COLOR;
EnableColorGlyphPainting(mtlc);
}
MTLPooledTextureHandle* texHandle = [mtlc.texturePool getTexture:w height:h format:MTLPixelFormatBGRA8Unorm];
if (texHandle == nil) {
J2dTraceLn(J2D_TRACE_ERROR, "MTLTR_DrawColorGlyphNoCache: can't obtain temporary texture object from pool");
return JNI_FALSE;
}
[[mtlc getCommandBufferWrapper] registerPooledTexture:texHandle];
[texHandle.texture replaceRegion:MTLRegionMake2D(0, 0, w, h)
mipmapLevel:0
withBytes:src
bytesPerRow:rowBytes];
drawTex2Tex(mtlc, texHandle.texture, dest, JNI_FALSE, dstOps->isOpaque, INTERPOLATION_NEAREST_NEIGHBOR,
0, 0, w, h, x, y, x + w, y + h);
return JNI_TRUE;
}
// see DrawGlyphList.c for more on this macro...
#define FLOOR_ASSIGN(l, r) \
if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
@ -614,7 +680,7 @@ MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
J2dTraceLn(J2D_TRACE_INFO, "Entered for loop for glyph list");
jint x, y;
jfloat glyphx, glyphy;
jboolean grayscale, ok;
jboolean ok;
GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
if (ginfo == NULL) {
@ -624,8 +690,6 @@ MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
break;
}
grayscale = (ginfo->rowBytes == ginfo->width);
if (usePositions) {
jfloat posx = NEXT_FLOAT(positions);
jfloat posy = NEXT_FLOAT(positions);
@ -649,7 +713,7 @@ MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
J2dTraceLn2(J2D_TRACE_INFO, "Glyph width = %d height = %d", ginfo->width, ginfo->height);
J2dTraceLn1(J2D_TRACE_INFO, "rowBytes = %d", ginfo->rowBytes);
if (grayscale) {
if (ginfo->rowBytes == ginfo->width) {
// grayscale or monochrome glyph data
if (ginfo->width <= MTLTR_CACHE_CELL_WIDTH &&
ginfo->height <= MTLTR_CACHE_CELL_HEIGHT)
@ -660,6 +724,10 @@ MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList Grayscale no cache");
ok = MTLTR_DrawGrayscaleGlyphNoCache(mtlc, ginfo, x, y, dstOps);
}
} else if (ginfo->rowBytes == ginfo->width * 4) {
J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList color glyph no cache");
ok = MTLTR_DrawColorGlyphNoCache(mtlc, ginfo, x, y, dstOps);
flushBeforeLCD = JNI_FALSE;
} else {
if (!flushBeforeLCD) {
[mtlc.encoderManager endEncoder];
@ -718,6 +786,8 @@ MTLTR_DrawGlyphList(JNIEnv *env, MTLContext *mtlc, BMTLSDOps *dstOps,
} else if (glyphMode == MODE_USE_CACHE_LCD) {
[mtlc.encoderManager endEncoder];
lcdCacheEncoder = nil;
} else if (glyphMode == MODE_NO_CACHE_COLOR) {
DisableColorGlyphPainting(mtlc);
}
}

@ -0,0 +1,71 @@
/*
* Copyright 2021 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.font;
import sun.java2d.SurfaceData;
import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
import java.awt.image.Raster;
/**
* SurfaceData view for a color glyph from glyph cache
*/
class ColorGlyphSurfaceData extends SurfaceData {
ColorGlyphSurfaceData() {
super(State.UNTRACKABLE);
initOps();
}
private native void initOps();
native void setCurrentGlyph(long imgPtr);
@Override
public SurfaceData getReplacement() {
throw new UnsupportedOperationException();
}
@Override
public GraphicsConfiguration getDeviceConfiguration() {
throw new UnsupportedOperationException();
}
@Override
public Raster getRaster(int x, int y, int w, int h) {
throw new UnsupportedOperationException();
}
@Override
public Rectangle getBounds() {
throw new UnsupportedOperationException();
}
@Override
public Object getDestination() {
throw new UnsupportedOperationException();
}
}

@ -25,10 +25,10 @@
package sun.font;
import java.awt.Font;
import java.awt.font.GlyphVector;
import java.awt.font.FontRenderContext;
import java.util.concurrent.atomic.AtomicBoolean;
import sun.java2d.SurfaceData;
import sun.java2d.loops.FontInfo;
/*
@ -52,7 +52,7 @@ import sun.java2d.loops.FontInfo;
* GlyphList gl = GlyphList.getInstance();
* try {
* gl.setFromString(info, str, x, y);
* int strbounds[] = gl.getBounds();
* gl.startGlyphIteration();
* int numglyphs = gl.getNumGlyphs();
* for (int i = 0; i < numglyphs; i++) {
* gl.setGlyphIndex(i);
@ -155,6 +155,7 @@ public final class GlyphList {
private static final GlyphList reusableGL = new GlyphList();
private static final AtomicBoolean inUse = new AtomicBoolean();
private ColorGlyphSurfaceData glyphSurfaceData;
void ensureCapacity(int len) {
/* Note len must not be -ve! only setFromChars should be capable
@ -276,13 +277,9 @@ public final class GlyphList {
glyphindex = -1;
}
public int[] getBounds() {
/* We co-opt the 5 element array that holds per glyph metrics in order
* to return the bounds. So a caller must copy the data out of the
* array before calling any other methods on this GlyphList
*/
public void startGlyphIteration() {
if (glyphindex >= 0) {
throw new InternalError("calling getBounds after setGlyphIndex");
throw new InternalError("glyph iteration restarted");
}
if (metrics == null) {
metrics = new int[5];
@ -291,7 +288,18 @@ public final class GlyphList {
* Add 0.5f for consistent rounding to pixel position. */
gposx = x + 0.5f;
gposy = y + 0.5f;
fillBounds(metrics);
}
/*
* Must be called after 'startGlyphIteration'.
* Returns overall bounds for glyphs starting from the next glyph
* in iteration till the glyph with specified index.
* The underlying storage for bounds is shared with metrics,
* so this method (and the array it returns) shouldn't be used between
* 'setGlyphIndex' call and matching 'getMetrics' call.
*/
public int[] getBounds(int endGlyphIndex) {
fillBounds(metrics, endGlyphIndex);
return metrics;
}
@ -436,7 +444,7 @@ public final class GlyphList {
/* We re-do all this work as we iterate through the glyphs
* but it seems unavoidable without re-working the Java TextRenderers.
*/
private void fillBounds(int[] bounds) {
private void fillBounds(int[] bounds, int endGlyphIndex) {
/* Faster to access local variables in the for loop? */
int xOffset = StrikeCache.topLeftXOffset;
int yOffset = StrikeCache.topLeftYOffset;
@ -445,7 +453,8 @@ public final class GlyphList {
int xAdvOffset = StrikeCache.xAdvanceOffset;
int yAdvOffset = StrikeCache.yAdvanceOffset;
if (len == 0) {
int startGlyphIndex = glyphindex + 1;
if (startGlyphIndex >= endGlyphIndex) {
bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0;
return;
}
@ -453,12 +462,12 @@ public final class GlyphList {
bx0 = by0 = Float.POSITIVE_INFINITY;
bx1 = by1 = Float.NEGATIVE_INFINITY;
int posIndex = 0;
float glx = x + 0.5f;
float gly = y + 0.5f;
int posIndex = startGlyphIndex<<1;
float glx = gposx;
float gly = gposy;
char gw, gh;
float gx, gy, gx0, gy0, gx1, gy1;
for (int i=0; i<len; i++) {
for (int i=startGlyphIndex; i<endGlyphIndex; i++) {
if (images[i] == 0L) {
continue;
}
@ -491,4 +500,24 @@ public final class GlyphList {
bounds[2] = (int)Math.floor(bx1);
bounds[3] = (int)Math.floor(by1);
}
public static boolean canContainColorGlyphs() {
return FontUtilities.isMacOSX;
}
public boolean isColorGlyph(int glyphIndex) {
int width = StrikeCache.unsafe.getChar(images[glyphIndex] +
StrikeCache.widthOffset);
int rowBytes = StrikeCache.unsafe.getChar(images[glyphIndex] +
StrikeCache.rowBytesOffset);
return rowBytes == width * 4;
}
public SurfaceData getColorGlyphData() {
if (glyphSurfaceData == null) {
glyphSurfaceData = new ColorGlyphSurfaceData();
}
glyphSurfaceData.setCurrentGlyph(images[glyphindex]);
return glyphSurfaceData;
}
}

@ -53,6 +53,7 @@ import sun.java2d.loops.FontInfo;
import sun.java2d.loops.DrawGlyphList;
import sun.java2d.loops.DrawGlyphListAA;
import sun.java2d.loops.DrawGlyphListLCD;
import sun.java2d.loops.DrawGlyphListColor;
import sun.java2d.pipe.LoopPipe;
import sun.java2d.pipe.ShapeDrawPipe;
import sun.java2d.pipe.ParallelogramPipe;
@ -892,6 +893,8 @@ public abstract class SurfaceData
loops.drawGlyphListLoop = DrawGlyphList.locate(src, comp, dst);
loops.drawGlyphListAALoop = DrawGlyphListAA.locate(src, comp, dst);
loops.drawGlyphListLCDLoop = DrawGlyphListLCD.locate(src, comp, dst);
loops.drawGlyphListColorLoop =
DrawGlyphListColor.locate(src, comp, dst);
/*
System.out.println("drawLine: "+loops.drawLineLoop);
System.out.println("fillRect: "+loops.fillRectLoop);

@ -68,7 +68,8 @@ public class DrawGlyphList extends GraphicsPrimitive {
public native void DrawGlyphList(SunGraphics2D sg2d, SurfaceData dest,
GlyphList srcData);
GlyphList srcData,
int fromGlyph, int toGlyph);
// This instance is used only for lookup.
static {
@ -94,16 +95,14 @@ public class DrawGlyphList extends GraphicsPrimitive {
}
public void DrawGlyphList(SunGraphics2D sg2d, SurfaceData dest,
GlyphList gl) {
GlyphList gl, int fromGlyph, int toGlyph) {
int[] strbounds = gl.getBounds(); // Don't delete, bug 4895493
int num = gl.getNumGlyphs();
Region clip = sg2d.getCompClip();
int cx1 = clip.getLoX();
int cy1 = clip.getLoY();
int cx2 = clip.getHiX();
int cy2 = clip.getHiY();
for (int i = 0; i < num; i++) {
for (int i = fromGlyph; i < toGlyph; i++) {
gl.setGlyphIndex(i);
int[] metrics = gl.getMetrics();
int gx1 = metrics[0];
@ -151,10 +150,10 @@ public class DrawGlyphList extends GraphicsPrimitive {
}
public void DrawGlyphList(SunGraphics2D sg2d, SurfaceData dest,
GlyphList glyphs)
GlyphList glyphs, int fromGlyph, int toGlyph)
{
tracePrimitive(target);
target.DrawGlyphList(sg2d, dest, glyphs);
target.DrawGlyphList(sg2d, dest, glyphs, fromGlyph, toGlyph);
}
}
}

@ -67,7 +67,8 @@ public class DrawGlyphListAA extends GraphicsPrimitive {
}
public native void DrawGlyphListAA(SunGraphics2D sg2d, SurfaceData dest,
GlyphList srcData);
GlyphList srcData,
int fromGlyph, int toGlyph);
static {
GraphicsPrimitiveMgr.registerGeneral(
@ -92,16 +93,14 @@ public class DrawGlyphListAA extends GraphicsPrimitive {
}
public void DrawGlyphListAA(SunGraphics2D sg2d, SurfaceData dest,
GlyphList gl)
GlyphList gl, int fromGlyph, int toGlyph)
{
gl.getBounds(); // Don't delete, bug 4895493
int num = gl.getNumGlyphs();
Region clip = sg2d.getCompClip();
int cx1 = clip.getLoX();
int cy1 = clip.getLoY();
int cx2 = clip.getHiX();
int cy2 = clip.getHiY();
for (int i = 0; i < num; i++) {
for (int i = fromGlyph; i < toGlyph; i++) {
gl.setGlyphIndex(i);
int[] metrics = gl.getMetrics();
int gx1 = metrics[0];
@ -149,10 +148,11 @@ public class DrawGlyphListAA extends GraphicsPrimitive {
}
public void DrawGlyphListAA(SunGraphics2D sg2d, SurfaceData dest,
GlyphList glyphs)
GlyphList glyphs,
int fromGlyph, int toGlyph)
{
tracePrimitive(target);
target.DrawGlyphListAA(sg2d, dest, glyphs);
target.DrawGlyphListAA(sg2d, dest, glyphs, fromGlyph, toGlyph);
}
}
}

@ -0,0 +1,148 @@
/*
* Copyright 2021 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.loops;
import sun.font.GlyphList;
import sun.java2d.SunGraphics2D;
import sun.java2d.SurfaceData;
import sun.java2d.pipe.Region;
import java.awt.*;
/**
* Draws color glyphs onto destination surface
*/
public class DrawGlyphListColor extends GraphicsPrimitive {
public final static String methodSignature =
"DrawGlyphListColor(...)".toString();
public final static int primTypeID = makePrimTypeID();
public static DrawGlyphListColor locate(SurfaceType srctype,
CompositeType comptype,
SurfaceType dsttype)
{
return (DrawGlyphListColor)
GraphicsPrimitiveMgr.locate(primTypeID,
srctype, comptype, dsttype);
}
protected DrawGlyphListColor(SurfaceType srctype,
CompositeType comptype,
SurfaceType dsttype)
{
super(methodSignature, primTypeID, srctype, comptype, dsttype);
}
public void DrawGlyphListColor(SunGraphics2D sg2d, SurfaceData dest,
GlyphList srcData,
int fromGlyph, int toGlyph) {
// actual implementation is in the 'General' subclass
}
// This instance is used only for lookup.
static {
GraphicsPrimitiveMgr.registerGeneral(
new DrawGlyphListColor(null, null, null));
}
public GraphicsPrimitive makePrimitive(SurfaceType srctype,
CompositeType comptype,
SurfaceType dsttype) {
return new General(srctype, comptype, dsttype);
}
private static class General extends DrawGlyphListColor {
private final Blit blit;
public General(SurfaceType srctype,
CompositeType comptype,
SurfaceType dsttype)
{
super(srctype, comptype, dsttype);
blit = Blit.locate(SurfaceType.IntArgbPre,
CompositeType.SrcOverNoEa, dsttype);
}
public void DrawGlyphListColor(SunGraphics2D sg2d, SurfaceData dest,
GlyphList gl, int fromGlyph, int toGlyph) {
Region clip = sg2d.getCompClip();
int cx1 = clip.getLoX();
int cy1 = clip.getLoY();
int cx2 = clip.getHiX();
int cy2 = clip.getHiY();
for (int i = fromGlyph; i < toGlyph; i++) {
gl.setGlyphIndex(i);
int[] metrics = gl.getMetrics();
int x = metrics[0];
int y = metrics[1];
int w = metrics[2];
int h = metrics[3];
int gx1 = x;
int gy1 = y;
int gx2 = x + w;
int gy2 = y + h;
if (gx1 < cx1) gx1 = cx1;
if (gy1 < cy1) gy1 = cy1;
if (gx2 > cx2) gx2 = cx2;
if (gy2 > cy2) gy2 = cy2;
if (gx2 > gx1 && gy2 > gy1) {
blit.Blit(gl.getColorGlyphData(), dest, AlphaComposite.SrcOver, clip,
gx1 - x, gy1 - y, gx1, gy1, gx2 - gx1, gy2 - gy1);
}
}
}
}
public GraphicsPrimitive traceWrap() {
return new TraceDrawGlyphListColor(this);
}
private static class TraceDrawGlyphListColor extends DrawGlyphListColor {
DrawGlyphListColor target;
public TraceDrawGlyphListColor(DrawGlyphListColor target) {
super(target.getSourceType(),
target.getCompositeType(),
target.getDestType());
this.target = target;
}
public GraphicsPrimitive traceWrap() {
return this;
}
public void DrawGlyphListColor(SunGraphics2D sg2d, SurfaceData dest,
GlyphList glyphs, int fromGlyph, int toGlyph)
{
tracePrimitive(target);
target.DrawGlyphListColor(sg2d, dest, glyphs, fromGlyph, toGlyph);
}
}
}

@ -68,7 +68,8 @@ public class DrawGlyphListLCD extends GraphicsPrimitive {
}
public native void DrawGlyphListLCD(SunGraphics2D sg2d, SurfaceData dest,
GlyphList srcData);
GlyphList srcData,
int fromGlyph, int toGlyph);
public GraphicsPrimitive traceWrap() {
return new TraceDrawGlyphListLCD(this);
@ -89,10 +90,11 @@ public class DrawGlyphListLCD extends GraphicsPrimitive {
}
public void DrawGlyphListLCD(SunGraphics2D sg2d, SurfaceData dest,
GlyphList glyphs)
GlyphList glyphs,
int fromGlyph, int toGlyph)
{
tracePrimitive(target);
target.DrawGlyphListLCD(sg2d, dest, glyphs);
target.DrawGlyphListLCD(sg2d, dest, glyphs, fromGlyph, toGlyph);
}
}
}

@ -365,9 +365,10 @@ public final class GeneralRenderer {
* reset the glyphs to non-AA after construction.
*/
static void doDrawGlyphList(SurfaceData sData, PixelWriter pw,
GlyphList gl, Region clip)
GlyphList gl, int fromGlyph, int toGlyph,
Region clip)
{
int[] bounds = gl.getBounds();
int[] bounds = gl.getBounds(toGlyph);
clip.clipBoxToBounds(bounds);
int cx1 = bounds[0];
int cy1 = bounds[1];
@ -378,8 +379,7 @@ public final class GeneralRenderer {
(WritableRaster) sData.getRaster(cx1, cy1, cx2 - cx1, cy2 - cy1);
pw.setRaster(dstRast);
int num = gl.getNumGlyphs();
for (int i = 0; i < num; i++) {
for (int i = fromGlyph; i < toGlyph; i++) {
gl.setGlyphIndex(i);
int[] metrics = gl.getMetrics();
int gx1 = metrics[0];
@ -971,10 +971,11 @@ class XorDrawGlyphListANY extends DrawGlyphList {
}
public void DrawGlyphList(SunGraphics2D sg2d, SurfaceData sData,
GlyphList gl)
GlyphList gl, int fromGlyph, int toGlyph)
{
PixelWriter pw = GeneralRenderer.createXorPixelWriter(sg2d, sData);
GeneralRenderer.doDrawGlyphList(sData, pw, gl, sg2d.getCompClip());
GeneralRenderer.doDrawGlyphList(sData, pw, gl, fromGlyph, toGlyph,
sg2d.getCompClip());
}
}
@ -986,10 +987,11 @@ class XorDrawGlyphListAAANY extends DrawGlyphListAA {
}
public void DrawGlyphListAA(SunGraphics2D sg2d, SurfaceData sData,
GlyphList gl)
GlyphList gl, int fromGlyph, int toGlyph)
{
PixelWriter pw = GeneralRenderer.createXorPixelWriter(sg2d, sData);
GeneralRenderer.doDrawGlyphList(sData, pw, gl, sg2d.getCompClip());
GeneralRenderer.doDrawGlyphList(sData, pw, gl, fromGlyph, toGlyph,
sg2d.getCompClip());
}
}

@ -52,4 +52,5 @@ public class RenderLoops {
public DrawGlyphList drawGlyphListLoop;
public DrawGlyphListAA drawGlyphListAALoop;
public DrawGlyphListLCD drawGlyphListLCDLoop;
public DrawGlyphListColor drawGlyphListColorLoop;
}

@ -25,6 +25,7 @@
package sun.java2d.pipe;
import sun.awt.SunHints;
import sun.java2d.SunGraphics2D;
import sun.font.GlyphList;
@ -37,8 +38,7 @@ public class AATextRenderer extends GlyphListLoopPipe
implements LoopBasedPipe
{
protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) {
sg2d.loops.drawGlyphListAALoop.DrawGlyphListAA(sg2d, sg2d.surfaceData,
gl);
drawGlyphList(sg2d, gl, SunHints.INTVAL_TEXT_ANTIALIAS_ON);
}
}

@ -40,20 +40,51 @@ public abstract class GlyphListLoopPipe extends GlyphListPipe
{
protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl,
int aaHint) {
switch (aaHint) {
case SunHints.INTVAL_TEXT_ANTIALIAS_OFF:
sg2d.loops.drawGlyphListLoop.
DrawGlyphList(sg2d, sg2d.surfaceData, gl);
return;
case SunHints.INTVAL_TEXT_ANTIALIAS_ON:
sg2d.loops.drawGlyphListAALoop.
DrawGlyphListAA(sg2d, sg2d.surfaceData, gl);
return;
case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB:
case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB:
sg2d.loops.drawGlyphListLCDLoop.
DrawGlyphListLCD(sg2d,sg2d.surfaceData, gl);
return;
int prevLimit = 0;
boolean isColor = false;
int len = gl.getNumGlyphs();
gl.startGlyphIteration();
if (GlyphList.canContainColorGlyphs()) {
for (int i = 0; i < len; i++) {
boolean newIsColor = gl.isColorGlyph(i);
if (newIsColor != isColor) {
drawGlyphListSegment(sg2d, gl, prevLimit, i, aaHint,
isColor);
prevLimit = i;
isColor = newIsColor;
}
}
}
drawGlyphListSegment(sg2d, gl, prevLimit, len, aaHint, isColor);
}
private void drawGlyphListSegment(SunGraphics2D sg2d, GlyphList gl,
int fromglyph, int toGlyph,
int aaHint, boolean isColor) {
if (fromglyph >= toGlyph) return;
if (isColor) {
sg2d.loops.drawGlyphListColorLoop.
DrawGlyphListColor(sg2d, sg2d.surfaceData,
gl, fromglyph, toGlyph);
} else {
switch (aaHint) {
case SunHints.INTVAL_TEXT_ANTIALIAS_OFF:
sg2d.loops.drawGlyphListLoop.
DrawGlyphList(sg2d, sg2d.surfaceData,
gl, fromglyph, toGlyph);
return;
case SunHints.INTVAL_TEXT_ANTIALIAS_ON:
sg2d.loops.drawGlyphListAALoop.
DrawGlyphListAA(sg2d, sg2d.surfaceData,
gl, fromglyph, toGlyph);
return;
case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB:
case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB:
sg2d.loops.drawGlyphListLCDLoop.
DrawGlyphListLCD(sg2d, sg2d.surfaceData,
gl, fromglyph, toGlyph);
return;
}
}
}
}

@ -25,10 +25,9 @@
package sun.java2d.pipe;
import java.awt.font.GlyphVector;
import sun.awt.SunHints;
import sun.java2d.SunGraphics2D;
import sun.font.GlyphList;
import static sun.awt.SunHints.*;
/**
* A delegate pipe of SG2D for drawing LCD text with
@ -38,7 +37,7 @@ import static sun.awt.SunHints.*;
public class LCDTextRenderer extends GlyphListLoopPipe {
protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) {
sg2d.loops.drawGlyphListLCDLoop.
DrawGlyphListLCD(sg2d, sg2d.surfaceData, gl);
drawGlyphList(sg2d, gl, SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB);
}
}

@ -25,6 +25,7 @@
package sun.java2d.pipe;
import sun.awt.SunHints;
import sun.java2d.SunGraphics2D;
import sun.font.GlyphList;
@ -37,6 +38,6 @@ public class SolidTextRenderer extends GlyphListLoopPipe
implements LoopBasedPipe
{
protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) {
sg2d.loops.drawGlyphListLoop.DrawGlyphList(sg2d, sg2d.surfaceData, gl);
drawGlyphList(sg2d, gl, SunHints.INTVAL_TEXT_ANTIALIAS_OFF);
}
}

@ -51,7 +51,8 @@ public class TextRenderer extends GlyphListPipe {
int cy2 = clipRegion.getHiY();
Object ctx = null;
try {
int[] bounds = gl.getBounds();
gl.startGlyphIteration();
int[] bounds = gl.getBounds(num);
Rectangle r = new Rectangle(bounds[0], bounds[1],
bounds[2] - bounds[0],
bounds[3] - bounds[1]);
@ -76,7 +77,7 @@ public class TextRenderer extends GlyphListPipe {
}
if (gx2 > cx2) gx2 = cx2;
if (gy2 > cy2) gy2 = cy2;
if (gx2 > gx1 && gy2 > gy1 &&
if (gx2 > gx1 && gy2 > gy1 && !gl.isColorGlyph(i) &&
outpipe.needTile(ctx, gx1, gy1, gx2 - gx1, gy2 - gy1))
{
byte[] alpha = gl.getGrayBits();

@ -63,7 +63,8 @@ typedef enum {
MODE_USE_CACHE_GRAY,
MODE_USE_CACHE_LCD,
MODE_NO_CACHE_GRAY,
MODE_NO_CACHE_LCD
MODE_NO_CACHE_LCD,
MODE_NO_CACHE_COLOR
} GlyphMode;
static GlyphMode glyphMode = MODE_NOT_INITED;
@ -526,6 +527,7 @@ OGLTR_DisableGlyphModeState()
j2d_glDisable(GL_TEXTURE_2D);
break;
case MODE_NO_CACHE_COLOR:
case MODE_NO_CACHE_GRAY:
case MODE_USE_CACHE_GRAY:
case MODE_NOT_INITED:
@ -978,6 +980,33 @@ OGLTR_DrawLCDGlyphNoCache(OGLContext *oglc, OGLSDOps *dstOps,
return JNI_TRUE;
}
static jboolean
OGLTR_DrawColorGlyphNoCache(OGLContext *oglc, GlyphInfo *ginfo, jint x, jint y)
{
if (glyphMode != MODE_NO_CACHE_COLOR) {
OGLTR_DisableGlyphModeState();
RESET_PREVIOUS_OP();
glyphMode = MODE_NO_CACHE_COLOR;
}
// see OGLBlitSwToSurface() in OGLBlitLoops.c
// for more info on the following two lines
j2d_glRasterPos2i(0, 0);
j2d_glBitmap(0, 0, 0, 0, (GLfloat) x, (GLfloat) (-y), NULL);
// in OpenGL image data is assumed to contain lines from bottom to top
j2d_glPixelZoom(1, -1);
j2d_glDrawPixels(ginfo->width, ginfo->height, GL_BGRA, GL_UNSIGNED_BYTE,
ginfo->image);
// restoring state
j2d_glPixelZoom(1, 1);
return JNI_TRUE;
}
// see DrawGlyphList.c for more on this macro...
#define FLOOR_ASSIGN(l, r) \
if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
@ -1028,7 +1057,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
jint x, y;
jfloat glyphx, glyphy;
jboolean grayscale, ok;
jboolean ok;
GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
if (ginfo == NULL) {
@ -1038,8 +1067,6 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
break;
}
grayscale = (ginfo->rowBytes == ginfo->width);
if (usePositions) {
jfloat posx = NEXT_FLOAT(positions);
jfloat posy = NEXT_FLOAT(positions);
@ -1060,7 +1087,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
continue;
}
if (grayscale) {
if (ginfo->rowBytes == ginfo->width) {
// grayscale or monochrome glyph data
if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
@ -1069,6 +1096,9 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
} else {
ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
}
} else if (ginfo->rowBytes == ginfo->width * 4) {
// color glyph data
ok = OGLTR_DrawColorGlyphNoCache(oglc, ginfo, x, y);
} else {
// LCD-optimized glyph data
jint rowBytesOffset = 0;

@ -0,0 +1,93 @@
/*
* Copyright 2021 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni_util.h"
#include "fontscalerdefs.h"
#include "SurfaceData.h"
typedef struct _GlyphOps {
SurfaceDataOps sdOps;
GlyphInfo* glyph;
} GlyphOps;
static jint Glyph_Lock(JNIEnv *env,
SurfaceDataOps *ops,
SurfaceDataRasInfo *pRasInfo,
jint lockflags)
{
SurfaceDataBounds bounds;
GlyphInfo *glyph;
if (lockflags &
(SD_LOCK_WRITE | SD_LOCK_LUT | SD_LOCK_INVCOLOR | SD_LOCK_INVGRAY)) {
JNU_ThrowInternalError(env, "Unsupported mode for glyph image surface");
return SD_FAILURE;
}
glyph = ((GlyphOps*)ops)->glyph;
bounds.x1 = 0;
bounds.y1 = 0;
bounds.x2 = glyph->width;
bounds.y2 = glyph->height;
SurfaceData_IntersectBounds(&pRasInfo->bounds, &bounds);
return SD_SUCCESS;
}
static void Glyph_GetRasInfo(JNIEnv *env,
SurfaceDataOps *ops,
SurfaceDataRasInfo *pRasInfo)
{
GlyphInfo *glyph = ((GlyphOps*)ops)->glyph;
pRasInfo->rasBase = glyph->image;
pRasInfo->pixelBitOffset = 0;
pRasInfo->pixelStride = 4;
pRasInfo->scanStride = glyph->rowBytes;
}
JNIEXPORT void JNICALL
Java_sun_font_ColorGlyphSurfaceData_initOps(JNIEnv *env,
jobject sData)
{
GlyphOps *ops =
(GlyphOps*) SurfaceData_InitOps(env, sData, sizeof(GlyphOps));
if (ops == NULL) {
JNU_ThrowOutOfMemoryError(env,
"Initialization of ColorGlyphSurfaceData failed");
return;
}
ops->sdOps.Lock = Glyph_Lock;
ops->sdOps.GetRasInfo = Glyph_GetRasInfo;
}
JNIEXPORT void JNICALL
Java_sun_font_ColorGlyphSurfaceData_setCurrentGlyph(JNIEnv *env,
jobject sData,
jlong imgPtr)
{
GlyphOps *ops = (GlyphOps*) SurfaceData_GetOps(env, sData);
if (ops == NULL) {
return;
}
ops->glyph = (GlyphInfo*) jlong_to_ptr(imgPtr);
}

@ -50,7 +50,8 @@
#define FLOOR_ASSIGN(l, r)\
if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))
GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist,
jint fromGlyph, jint toGlyph) {
int g;
size_t bytesNeeded;
@ -61,7 +62,7 @@ GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX);
jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY);
jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen);
jint len = toGlyph - fromGlyph;
jlongArray glyphImages = (jlongArray)
(*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages);
jfloatArray glyphPositions =
@ -84,13 +85,8 @@ GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
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;
int n = fromGlyph * 2 - 1;
positions =
(*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL);
@ -105,7 +101,7 @@ GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
jfloat px = x + positions[++n];
jfloat py = y + positions[++n];
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g]);
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g + fromGlyph]);
gbv->glyphs[g].glyphInfo = ginfo;
gbv->glyphs[g].pixels = ginfo->image;
gbv->glyphs[g].width = ginfo->width;
@ -118,7 +114,7 @@ GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
positions, JNI_ABORT);
} else {
for (g=0; g<len; g++) {
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g]);
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g + fromGlyph]);
gbv->glyphs[g].glyphInfo = ginfo;
gbv->glyphs[g].pixels = ginfo->image;
gbv->glyphs[g].width = ginfo->width;
@ -135,6 +131,12 @@ GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs,
JNI_ABORT);
if (!glyphPositions) {
(*env)->SetFloatField(env, glyphlist, sunFontIDs.glyphListX, x);
(*env)->SetFloatField(env, glyphlist, sunFontIDs.glyphListY, y);
}
return gbv;
}
@ -305,12 +307,13 @@ static void drawGlyphListLCD(JNIEnv *env, jobject self,
/*
* Class: sun_java2d_loops_DrawGlyphList
* Method: DrawGlyphList
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;II)V
*/
JNIEXPORT void JNICALL
Java_sun_java2d_loops_DrawGlyphList_DrawGlyphList
(JNIEnv *env, jobject self,
jobject sg2d, jobject sData, jobject glyphlist) {
jobject sg2d, jobject sData, jobject glyphlist,
jint fromGlyph, jint toGlyph) {
jint pixel, color;
GlyphBlitVector* gbv;
@ -320,7 +323,7 @@ Java_sun_java2d_loops_DrawGlyphList_DrawGlyphList
return;
}
if ((gbv = setupBlitVector(env, glyphlist)) == NULL) {
if ((gbv = setupBlitVector(env, glyphlist, fromGlyph, toGlyph)) == NULL) {
return;
}
@ -335,12 +338,13 @@ Java_sun_java2d_loops_DrawGlyphList_DrawGlyphList
/*
* Class: sun_java2d_loops_DrawGlyphListAA
* Method: DrawGlyphListAA
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;II)V
*/
JNIEXPORT void JNICALL
Java_sun_java2d_loops_DrawGlyphListAA_DrawGlyphListAA
(JNIEnv *env, jobject self,
jobject sg2d, jobject sData, jobject glyphlist) {
jobject sg2d, jobject sData, jobject glyphlist,
jint fromGlyph, jint toGlyph) {
jint pixel, color;
GlyphBlitVector* gbv;
@ -350,7 +354,7 @@ Java_sun_java2d_loops_DrawGlyphListAA_DrawGlyphListAA
return;
}
if ((gbv = setupBlitVector(env, glyphlist)) == NULL) {
if ((gbv = setupBlitVector(env, glyphlist, fromGlyph, toGlyph)) == NULL) {
return;
}
pixel = GrPrim_Sg2dGetPixel(env, sg2d);
@ -363,12 +367,13 @@ Java_sun_java2d_loops_DrawGlyphListAA_DrawGlyphListAA
/*
* Class: sun_java2d_loops_DrawGlyphListLCD
* Method: DrawGlyphListLCD
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V
* Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;II)V
*/
JNIEXPORT void JNICALL
Java_sun_java2d_loops_DrawGlyphListLCD_DrawGlyphListLCD
(JNIEnv *env, jobject self,
jobject sg2d, jobject sData, jobject glyphlist) {
jobject sg2d, jobject sData, jobject glyphlist,
jint fromGlyph, jint toGlyph) {
jint pixel, color, contrast;
jboolean rgbOrder;
@ -379,7 +384,8 @@ Java_sun_java2d_loops_DrawGlyphListLCD_DrawGlyphListLCD
return;
}
if ((gbv = setupLCDBlitVector(env, glyphlist)) == NULL) {
if ((gbv = setupLCDBlitVector(env, glyphlist, fromGlyph, toGlyph))
== NULL) {
return;
}
pixel = GrPrim_Sg2dGetPixel(env, sg2d);
@ -481,7 +487,8 @@ Java_sun_java2d_loops_DrawGlyphListLCD_DrawGlyphListLCD
* 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) {
GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist,
jint fromGlyph, jint toGlyph) {
int g;
size_t bytesNeeded;
@ -492,7 +499,7 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX);
jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY);
jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen);
jint len = toGlyph - fromGlyph;
jlongArray glyphImages = (jlongArray)
(*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages);
jfloatArray glyphPositions =
@ -531,7 +538,7 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
* heterogenous
*/
if (subPixPos && len > 0) {
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[0]);
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[fromGlyph]);
if (ginfo == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphImages,
imagePtrs, JNI_ABORT);
@ -543,16 +550,9 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
subPixPos = JNI_FALSE;
}
}
if (subPixPos) {
x += 0.1666667f;
y += 0.1666667f;
} else {
x += 0.5f;
y += 0.5f;
}
if (glyphPositions) {
int n = -1;
int n = fromGlyph * 2 - 1;
positions =
(*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL);
@ -566,7 +566,7 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
for (g=0; g<len; g++) {
jfloat px, py;
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g]);
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g + fromGlyph]);
if (ginfo == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphImages,
imagePtrs, JNI_ABORT);
@ -608,7 +608,12 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
*/
if (subPixPos) {
int frac;
float pos = px + ginfo->topLeftX;
float pos;
px += 0.1666667f - 0.5f;
py += 0.1666667f - 0.5f;
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
@ -647,7 +652,9 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
positions, JNI_ABORT);
} else {
for (g=0; g<len; g++) {
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g]);
jfloat px = x;
jfloat py = y;
ginfo = (GlyphInfo*)((uintptr_t)imagePtrs[g + fromGlyph]);
if (ginfo == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphImages,
imagePtrs, JNI_ABORT);
@ -662,7 +669,12 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
if (subPixPos) {
int frac;
float pos = x + ginfo->topLeftX;
float pos;
px += 0.1666667f - 0.5f;
py += 0.1666667f - 0.5f;
pos = px + ginfo->topLeftX;
FLOOR_ASSIGN(gbv->glyphs[g].x, pos);
frac = (int)((pos - gbv->glyphs[g].x)*3);
if (frac == 0) {
@ -672,10 +684,11 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
gbv->glyphs[g].x += 1;
}
} else {
FLOOR_ASSIGN(gbv->glyphs[g].x, x + ginfo->topLeftX);
FLOOR_ASSIGN(gbv->glyphs[g].x, px + ginfo->topLeftX);
gbv->glyphs[g].rowBytesOffset = 0;
}
FLOOR_ASSIGN(gbv->glyphs[g].y, y + ginfo->topLeftY);
FLOOR_ASSIGN(gbv->glyphs[g].y, py + ginfo->topLeftY);
/* copy image data into this array at x/y locations */
x += ginfo->advanceX;
y += ginfo->advanceY;
@ -684,6 +697,11 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs,
JNI_ABORT);
if (!glyphPositions) {
(*env)->SetFloatField(env, glyphlist, sunFontIDs.glyphListX, x);
(*env)->SetFloatField(env, glyphlist, sunFontIDs.glyphListY, y);
}
return gbv;
}

@ -40,8 +40,10 @@ typedef struct {
} GlyphBlitVector;
extern jint RefineBounds(GlyphBlitVector *gbv, SurfaceDataBounds *bounds);
extern GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist);
extern GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist);
extern GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist,
jint fromGlyph, jint toGlyph);
extern GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist,
jint fromGlyph, jint toGlyph);
#ifdef __cplusplus
}

@ -173,9 +173,9 @@ static void initFontIDs(JNIEnv *env) {
CHECK_NULL(tmpClass = (*env)->FindClass(env, "sun/font/GlyphList"));
CHECK_NULL(sunFontIDs.glyphListX =
(*env)->GetFieldID(env, tmpClass, "x", "F"));
(*env)->GetFieldID(env, tmpClass, "gposx", "F"));
CHECK_NULL(sunFontIDs.glyphListY =
(*env)->GetFieldID(env, tmpClass, "y", "F"));
(*env)->GetFieldID(env, tmpClass, "gposy", "F"));
CHECK_NULL(sunFontIDs.glyphListLen =
(*env)->GetFieldID(env, tmpClass, "len", "I"));
CHECK_NULL(sunFontIDs.glyphImages =

@ -77,6 +77,7 @@ public class X11TextRenderer extends GlyphListPipe {
Region clip = sg2d.getCompClip();
long xgc = x11sd.getRenderGC(clip, SunGraphics2D.COMP_ISCOPY,
null, sg2d.pixel);
gl.startGlyphIteration();
doDrawGlyphList(x11sd.getNativeOps(), xgc, clip, gl);
} finally {
SunToolkit.awtUnlock();

@ -83,7 +83,7 @@ public class XRTextRenderer extends GlyphListPipe {
int activeGlyphSet = cachedGlyphs[0].getGlyphSet();
int eltIndex = -1;
gl.getBounds();
gl.startGlyphIteration();
float[] positions = gl.getPositions();
for (int i = 0; i < gl.getNumGlyphs(); i++) {
gl.setGlyphIndex(i);

@ -57,11 +57,13 @@ JNIEXPORT void JNICALL Java_sun_font_X11TextRenderer_doDrawGlyphList
jlong dstData, jlong xgc, jobject clip,
jobject glyphlist)
{
jint glyphCount;
GlyphBlitVector* gbv;
SurfaceDataBounds bounds;
Region_GetBounds(env, clip, &bounds);
if ((gbv = setupBlitVector(env, glyphlist)) == NULL) {
glyphCount = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen);
if ((gbv = setupBlitVector(env, glyphlist, 0, glyphCount)) == NULL) {
return;
}
if (!RefineBounds(gbv, &bounds)) {

@ -0,0 +1,108 @@
/*
* Copyright 2021 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @key headful
* @bug 8263583
* @summary Checks that emoji character has a non-empty and identical
* representation when rendered to different types of images,
* including an accelerated (OpenGL or Metal) surface.
* @requires (os.family == "mac")
* @run main/othervm -Dsun.java2d.uiScale=1 MacEmoji
*/
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.util.List;
public class MacEmoji {
private static final int IMG_WIDTH = 20;
private static final int IMG_HEIGHT = 20;
public static void main(String[] args) {
GraphicsConfiguration cfg
= GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
VolatileImage vImg = cfg.createCompatibleVolatileImage(IMG_WIDTH,
IMG_HEIGHT);
BufferedImage refImg;
int attempt = 0;
do {
if (++attempt > 10) {
throw new RuntimeException("Failed to render to VolatileImage");
}
if (vImg.validate(cfg) == VolatileImage.IMAGE_INCOMPATIBLE) {
throw new RuntimeException("Unexpected validation failure");
}
drawEmoji(vImg);
refImg = vImg.getSnapshot();
} while (vImg.contentsLost());
boolean rendered = false;
for (int x = 0; x < IMG_WIDTH; x++) {
for (int y = 0; y < IMG_HEIGHT; y++) {
if (refImg.getRGB(x, y) != 0xFFFFFFFF) {
rendered = true;
break;
}
}
}
if (!rendered) {
throw new RuntimeException("Emoji character wasn't rendered");
}
List<Integer> imageTypes = List.of(
BufferedImage.TYPE_INT_RGB,
BufferedImage.TYPE_INT_ARGB,
BufferedImage.TYPE_INT_ARGB_PRE,
BufferedImage.TYPE_INT_BGR,
BufferedImage.TYPE_3BYTE_BGR,
BufferedImage.TYPE_4BYTE_ABGR,
BufferedImage.TYPE_4BYTE_ABGR_PRE
);
for (Integer type : imageTypes) {
BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
drawEmoji(img);
for (int x = 0; x < IMG_WIDTH; x++) {
for (int y = 0; y < IMG_HEIGHT; y++) {
if (refImg.getRGB(x, y) != img.getRGB(x, y)) {
throw new RuntimeException(
"Rendering differs for image type " + type);
}
}
}
}
}
private static void drawEmoji(Image img) {
Graphics g = img.getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);
g.setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
g.drawString("\uD83D\uDE00" /* U+1F600 'GRINNING FACE' */, 2, 15);
g.dispose();
}
}