1 | /* |
2 | * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. Oracle designates this |
8 | * particular file as subject to the "Classpath" exception as provided |
9 | * by Oracle in the LICENSE file that accompanied this code. |
10 | * |
11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * version 2 for more details (a copy is included in the LICENSE file that |
15 | * accompanied this code). |
16 | * |
17 | * You should have received a copy of the GNU General Public License version |
18 | * 2 along with this work; if not, write to the Free Software Foundation, |
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | * |
21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 | * or visit www.oracle.com if you need additional information or have any |
23 | * questions. |
24 | */ |
25 | |
26 | #include "jlong.h" |
27 | #include "math.h" |
28 | #include "string.h" |
29 | #include "stdlib.h" |
30 | #include "sunfontids.h" |
31 | #include "fontscalerdefs.h" |
32 | #include "glyphblitting.h" |
33 | #include "GraphicsPrimitiveMgr.h" |
34 | #include "sun_java2d_loops_DrawGlyphList.h" |
35 | #include "sun_java2d_loops_DrawGlyphListAA.h" |
36 | |
37 | |
38 | /* |
39 | * Need to account for the rare case when (eg) repainting damaged |
40 | * areas results in the drawing location being negative, in which |
41 | * case (int) rounding always goes towards zero. We need to always |
42 | * round down instead, so that we paint at the correct position. |
43 | * We only call "floor" when value is < 0 (ie rarely). |
44 | * Storing the result of (eg) (x+ginfo->topLeftX) benchmarks is more |
45 | * expensive than repeating the calculation as we do here. |
46 | * "floor" shows up as a significant cost in app-level microbenchmarks. |
47 | * This macro avoids calling it on positive values, instead using an |
48 | * (int) cast. |
49 | */ |
50 | #define FLOOR_ASSIGN(l, r)\ |
51 | if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r)) |
52 | |
53 | GlyphBlitVector* setupBlitVector(JNIEnv *env, jobject glyphlist) { |
54 | |
55 | int g; |
56 | size_t bytesNeeded; |
57 | jlong *imagePtrs; |
58 | jfloat* positions = NULL; |
59 | GlyphInfo *ginfo; |
60 | GlyphBlitVector *gbv; |
61 | |
62 | jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX); |
63 | jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY); |
64 | jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen); |
65 | jlongArray glyphImages = (jlongArray) |
66 | (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages); |
67 | jfloatArray glyphPositions = |
68 | (*env)->GetBooleanField(env, glyphlist, sunFontIDs.glyphListUsePos) |
69 | ? (jfloatArray) |
70 | (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphListPos) |
71 | : NULL; |
72 | |
73 | bytesNeeded = sizeof(GlyphBlitVector)+sizeof(ImageRef)*len; |
74 | gbv = (GlyphBlitVector*)malloc(bytesNeeded); |
75 | if (gbv == NULL) { |
76 | return NULL; |
77 | } |
78 | gbv->numGlyphs = len; |
79 | gbv->glyphs = (ImageRef*)((unsigned char*)gbv+sizeof(GlyphBlitVector)); |
80 | |
81 | imagePtrs = (*env)->GetPrimitiveArrayCritical(env, glyphImages, NULL); |
82 | if (imagePtrs == NULL) { |
83 | free(gbv); |
84 | return (GlyphBlitVector*)NULL; |
85 | } |
86 | |
87 | /* Add 0.5 to x and y and then use floor (or an equivalent operation) |
88 | * to round down the glyph positions to integral pixel positions. |
89 | */ |
90 | x += 0.5f; |
91 | y += 0.5f; |
92 | if (glyphPositions) { |
93 | int n = -1; |
94 | |
95 | positions = |
96 | (*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL); |
97 | if (positions == NULL) { |
98 | (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, |
99 | imagePtrs, JNI_ABORT); |
100 | free(gbv); |
101 | return (GlyphBlitVector*)NULL; |
102 | } |
103 | |
104 | for (g=0; g<len; g++) { |
105 | jfloat px = x + positions[++n]; |
106 | jfloat py = y + positions[++n]; |
107 | |
108 | ginfo = (GlyphInfo*)imagePtrs[g]; |
109 | gbv->glyphs[g].glyphInfo = ginfo; |
110 | gbv->glyphs[g].pixels = ginfo->image; |
111 | gbv->glyphs[g].width = ginfo->width; |
112 | gbv->glyphs[g].rowBytes = ginfo->rowBytes; |
113 | gbv->glyphs[g].height = ginfo->height; |
114 | FLOOR_ASSIGN(gbv->glyphs[g].x, px + ginfo->topLeftX); |
115 | FLOOR_ASSIGN(gbv->glyphs[g].y, py + ginfo->topLeftY); |
116 | } |
117 | (*env)->ReleasePrimitiveArrayCritical(env,glyphPositions, |
118 | positions, JNI_ABORT); |
119 | } else { |
120 | for (g=0; g<len; g++) { |
121 | ginfo = (GlyphInfo*)imagePtrs[g]; |
122 | gbv->glyphs[g].glyphInfo = ginfo; |
123 | gbv->glyphs[g].pixels = ginfo->image; |
124 | gbv->glyphs[g].width = ginfo->width; |
125 | gbv->glyphs[g].rowBytes = ginfo->rowBytes; |
126 | gbv->glyphs[g].height = ginfo->height; |
127 | FLOOR_ASSIGN(gbv->glyphs[g].x, x + ginfo->topLeftX); |
128 | FLOOR_ASSIGN(gbv->glyphs[g].y, y + ginfo->topLeftY); |
129 | |
130 | /* copy image data into this array at x/y locations */ |
131 | x += ginfo->advanceX; |
132 | y += ginfo->advanceY; |
133 | } |
134 | } |
135 | |
136 | (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, |
137 | JNI_ABORT); |
138 | return gbv; |
139 | } |
140 | |
141 | jint RefineBounds(GlyphBlitVector *gbv, SurfaceDataBounds *bounds) { |
142 | int index; |
143 | jint dx1, dy1, dx2, dy2; |
144 | ImageRef glyphImage; |
145 | int num = gbv->numGlyphs; |
146 | SurfaceDataBounds glyphs; |
147 | |
148 | glyphs.x1 = glyphs.y1 = 0x7fffffff; |
149 | glyphs.x2 = glyphs.y2 = 0x80000000; |
150 | for (index = 0; index < num; index++) { |
151 | glyphImage = gbv->glyphs[index]; |
152 | dx1 = (jint) glyphImage.x; |
153 | dy1 = (jint) glyphImage.y; |
154 | dx2 = dx1 + glyphImage.width; |
155 | dy2 = dy1 + glyphImage.height; |
156 | if (glyphs.x1 > dx1) glyphs.x1 = dx1; |
157 | if (glyphs.y1 > dy1) glyphs.y1 = dy1; |
158 | if (glyphs.x2 < dx2) glyphs.x2 = dx2; |
159 | if (glyphs.y2 < dy2) glyphs.y2 = dy2; |
160 | } |
161 | |
162 | SurfaceData_IntersectBounds(bounds, &glyphs); |
163 | return (bounds->x1 < bounds->x2 && bounds->y1 < bounds->y2); |
164 | } |
165 | |
166 | |
167 | |
168 | |
169 | /* since the AA and non-AA loop functions share a common method |
170 | * signature, can call both through this common function since |
171 | * there's no difference except for the inner loop. |
172 | * This could be a macro but there's enough of those already. |
173 | */ |
174 | static void drawGlyphList(JNIEnv *env, jobject self, |
175 | jobject sg2d, jobject sData, |
176 | GlyphBlitVector *gbv, jint pixel, jint color, |
177 | NativePrimitive *pPrim, DrawGlyphListFunc *func) { |
178 | |
179 | SurfaceDataOps *sdOps; |
180 | SurfaceDataRasInfo rasInfo; |
181 | CompositeInfo compInfo; |
182 | int clipLeft, clipRight, clipTop, clipBottom; |
183 | int ret; |
184 | |
185 | sdOps = SurfaceData_GetOps(env, sData); |
186 | if (sdOps == 0) { |
187 | return; |
188 | } |
189 | |
190 | if (pPrim->pCompType->getCompInfo != NULL) { |
191 | GrPrim_Sg2dGetCompInfo(env, sg2d, pPrim, &compInfo); |
192 | } |
193 | |
194 | GrPrim_Sg2dGetClip(env, sg2d, &rasInfo.bounds); |
195 | if (rasInfo.bounds.y2 <= rasInfo.bounds.y1 || |
196 | rasInfo.bounds.x2 <= rasInfo.bounds.x1) |
197 | { |
198 | return; |
199 | } |
200 | |
201 | ret = sdOps->Lock(env, sdOps, &rasInfo, pPrim->dstflags); |
202 | if (ret != SD_SUCCESS) { |
203 | if (ret == SD_SLOWLOCK) { |
204 | if (!RefineBounds(gbv, &rasInfo.bounds)) { |
205 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
206 | return; |
207 | } |
208 | } else { |
209 | return; |
210 | } |
211 | } |
212 | |
213 | sdOps->GetRasInfo(env, sdOps, &rasInfo); |
214 | if (!rasInfo.rasBase) { |
215 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
216 | return; |
217 | } |
218 | clipLeft = rasInfo.bounds.x1; |
219 | clipRight = rasInfo.bounds.x2; |
220 | clipTop = rasInfo.bounds.y1; |
221 | clipBottom = rasInfo.bounds.y2; |
222 | if (clipRight > clipLeft && clipBottom > clipTop) { |
223 | |
224 | (*func)(&rasInfo, |
225 | gbv->glyphs, gbv->numGlyphs, |
226 | pixel, color, |
227 | clipLeft, clipTop, |
228 | clipRight, clipBottom, |
229 | pPrim, &compInfo); |
230 | SurfaceData_InvokeRelease(env, sdOps, &rasInfo); |
231 | |
232 | } |
233 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
234 | } |
235 | |
236 | static unsigned char* getLCDGammaLUT(int gamma); |
237 | static unsigned char* getInvLCDGammaLUT(int gamma); |
238 | |
239 | static void drawGlyphListLCD(JNIEnv *env, jobject self, |
240 | jobject sg2d, jobject sData, |
241 | GlyphBlitVector *gbv, jint pixel, jint color, |
242 | jboolean rgbOrder, int contrast, |
243 | NativePrimitive *pPrim, |
244 | DrawGlyphListLCDFunc *func) { |
245 | |
246 | SurfaceDataOps *sdOps; |
247 | SurfaceDataRasInfo rasInfo; |
248 | CompositeInfo compInfo; |
249 | int clipLeft, clipRight, clipTop, clipBottom; |
250 | int ret; |
251 | |
252 | sdOps = SurfaceData_GetOps(env, sData); |
253 | if (sdOps == 0) { |
254 | return; |
255 | } |
256 | |
257 | if (pPrim->pCompType->getCompInfo != NULL) { |
258 | GrPrim_Sg2dGetCompInfo(env, sg2d, pPrim, &compInfo); |
259 | } |
260 | |
261 | GrPrim_Sg2dGetClip(env, sg2d, &rasInfo.bounds); |
262 | if (rasInfo.bounds.y2 <= rasInfo.bounds.y1 || |
263 | rasInfo.bounds.x2 <= rasInfo.bounds.x1) |
264 | { |
265 | return; |
266 | } |
267 | |
268 | ret = sdOps->Lock(env, sdOps, &rasInfo, pPrim->dstflags); |
269 | if (ret != SD_SUCCESS) { |
270 | if (ret == SD_SLOWLOCK) { |
271 | if (!RefineBounds(gbv, &rasInfo.bounds)) { |
272 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
273 | return; |
274 | } |
275 | } else { |
276 | return; |
277 | } |
278 | } |
279 | |
280 | sdOps->GetRasInfo(env, sdOps, &rasInfo); |
281 | if (!rasInfo.rasBase) { |
282 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
283 | return; |
284 | } |
285 | clipLeft = rasInfo.bounds.x1; |
286 | clipRight = rasInfo.bounds.x2; |
287 | clipTop = rasInfo.bounds.y1; |
288 | clipBottom = rasInfo.bounds.y2; |
289 | |
290 | if (clipRight > clipLeft && clipBottom > clipTop) { |
291 | |
292 | (*func)(&rasInfo, |
293 | gbv->glyphs, gbv->numGlyphs, |
294 | pixel, color, |
295 | clipLeft, clipTop, |
296 | clipRight, clipBottom, (jint)rgbOrder, |
297 | getLCDGammaLUT(contrast), getInvLCDGammaLUT(contrast), |
298 | pPrim, &compInfo); |
299 | SurfaceData_InvokeRelease(env, sdOps, &rasInfo); |
300 | |
301 | } |
302 | SurfaceData_InvokeUnlock(env, sdOps, &rasInfo); |
303 | } |
304 | |
305 | /* |
306 | * Class: sun_java2d_loops_DrawGlyphList |
307 | * Method: DrawGlyphList |
308 | * Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V |
309 | */ |
310 | JNIEXPORT void JNICALL |
311 | Java_sun_java2d_loops_DrawGlyphList_DrawGlyphList |
312 | (JNIEnv *env, jobject self, |
313 | jobject sg2d, jobject sData, jobject glyphlist) { |
314 | |
315 | jint pixel, color; |
316 | GlyphBlitVector* gbv; |
317 | NativePrimitive *pPrim; |
318 | |
319 | if ((pPrim = GetNativePrim(env, self)) == NULL) { |
320 | return; |
321 | } |
322 | |
323 | if ((gbv = setupBlitVector(env, glyphlist)) == NULL) { |
324 | return; |
325 | } |
326 | |
327 | pixel = GrPrim_Sg2dGetPixel(env, sg2d); |
328 | color = GrPrim_Sg2dGetEaRGB(env, sg2d); |
329 | drawGlyphList(env, self, sg2d, sData, gbv, pixel, color, |
330 | pPrim, pPrim->funcs.drawglyphlist); |
331 | free(gbv); |
332 | |
333 | } |
334 | |
335 | /* |
336 | * Class: sun_java2d_loops_DrawGlyphListAA |
337 | * Method: DrawGlyphListAA |
338 | * Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V |
339 | */ |
340 | JNIEXPORT void JNICALL |
341 | Java_sun_java2d_loops_DrawGlyphListAA_DrawGlyphListAA |
342 | (JNIEnv *env, jobject self, |
343 | jobject sg2d, jobject sData, jobject glyphlist) { |
344 | |
345 | jint pixel, color; |
346 | GlyphBlitVector* gbv; |
347 | NativePrimitive *pPrim; |
348 | |
349 | if ((pPrim = GetNativePrim(env, self)) == NULL) { |
350 | return; |
351 | } |
352 | |
353 | if ((gbv = setupBlitVector(env, glyphlist)) == NULL) { |
354 | return; |
355 | } |
356 | pixel = GrPrim_Sg2dGetPixel(env, sg2d); |
357 | color = GrPrim_Sg2dGetEaRGB(env, sg2d); |
358 | drawGlyphList(env, self, sg2d, sData, gbv, pixel, color, |
359 | pPrim, pPrim->funcs.drawglyphlistaa); |
360 | free(gbv); |
361 | } |
362 | |
363 | /* |
364 | * Class: sun_java2d_loops_DrawGlyphListLCD |
365 | * Method: DrawGlyphListLCD |
366 | * Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Lsun/java2d/font/GlyphList;J)V |
367 | */ |
368 | JNIEXPORT void JNICALL |
369 | Java_sun_java2d_loops_DrawGlyphListLCD_DrawGlyphListLCD |
370 | (JNIEnv *env, jobject self, |
371 | jobject sg2d, jobject sData, jobject glyphlist) { |
372 | |
373 | jint pixel, color, contrast; |
374 | jboolean rgbOrder; |
375 | GlyphBlitVector* gbv; |
376 | NativePrimitive *pPrim; |
377 | |
378 | if ((pPrim = GetNativePrim(env, self)) == NULL) { |
379 | return; |
380 | } |
381 | |
382 | if ((gbv = setupLCDBlitVector(env, glyphlist)) == NULL) { |
383 | return; |
384 | } |
385 | pixel = GrPrim_Sg2dGetPixel(env, sg2d); |
386 | color = GrPrim_Sg2dGetEaRGB(env, sg2d); |
387 | contrast = GrPrim_Sg2dGetLCDTextContrast(env, sg2d); |
388 | rgbOrder = (*env)->GetBooleanField(env,glyphlist, sunFontIDs.lcdRGBOrder); |
389 | drawGlyphListLCD(env, self, sg2d, sData, gbv, pixel, color, |
390 | rgbOrder, contrast, |
391 | pPrim, pPrim->funcs.drawglyphlistlcd); |
392 | free(gbv); |
393 | } |
394 | |
395 | /* |
396 | * LCD text utilises a filter which spreads energy to adjacent subpixels. |
397 | * So we add 3 bytes (one whole pixel) of padding at the start of every row |
398 | * to hold energy from the very leftmost sub-pixel. |
399 | * This is to the left of the intended glyph image position so LCD text also |
400 | * adjusts the top-left X position of the padded image one pixel to the left |
401 | * so a glyph image is drawn in the same place it would be if the padding |
402 | * were not present. |
403 | * |
404 | * So in the glyph cache for LCD text the first two bytes of every row are |
405 | * zero. |
406 | * We make use of this to be able to adjust the rendering position of the |
407 | * text when the client specifies a fractional metrics sub-pixel positioning |
408 | * rendering hint. |
409 | * |
410 | * So the first 6 bytes in a cache row looks like : |
411 | * 00 00 Ex G0 G1 G2 |
412 | * |
413 | * where |
414 | * 00 are the always zero bytes |
415 | * Ex is extra energy spread from the glyph into the left padding pixel. |
416 | * Gn are the RGB component bytes of the first pixel of the glyph image |
417 | * For an RGB display G0 is the red component, etc. |
418 | * |
419 | * If a glyph is drawn at X=12 then the G0 G1 G2 pixel is placed at that |
420 | * position : ie G0 is drawn in the first sub-pixel at X=12 |
421 | * |
422 | * Draw at X=12,0 |
423 | * PIXEL POS 11 11 11 12 12 12 13 13 13 |
424 | * SUBPX POS 0 1 2 0 1 2 0 1 2 |
425 | * 00 00 Ex G0 G1 G2 |
426 | * |
427 | * If a sub-pixel rounded glyph position is calculated as being X=12.33 - |
428 | * ie 12 and one-third pixels, we want the result to look like this : |
429 | * Draw at X=12,1 |
430 | * PIXEL POS 11 11 11 12 12 12 13 13 13 |
431 | * SUBPX POS 0 1 2 0 1 2 0 1 2 |
432 | * 00 00 Ex G0 G1 G2 |
433 | * |
434 | * ie the G0 byte is moved one sub-pixel to the right. |
435 | * To do this we need to make two adjustments : |
436 | * - set X=X+1 |
437 | * - set start of scan row to start+2, ie index past the two zero bytes |
438 | * ie we don't need the 00 00 bytes at all any more. Rendering start X |
439 | * can skip over those. |
440 | * |
441 | * Lets look at the final case : |
442 | * If a sub-pixel rounded glyph position is calculated as being X=12.67 - |
443 | * ie 12 and two-third pixels, we want the result to look like this : |
444 | * Draw at X=12,2 |
445 | * PIXEL POS 11 11 11 12 12 12 13 13 13 |
446 | * SUBPX POS 0 1 2 0 1 2 0 1 2 |
447 | * 00 00 Ex G0 G1 G2 |
448 | * |
449 | * ie the G0 byte is moved two sub-pixels to the right, so that the image |
450 | * starts at 12.67 |
451 | * To do this we need to make these two adjustments : |
452 | * - set X=X+1 |
453 | * - set start of scan row to start+1, ie index past the first zero byte |
454 | * In this case the second of the 00 bytes is used as a no-op on the first |
455 | * red sub-pixel position. |
456 | * |
457 | * The final adjustment needed to make all this work is note that if |
458 | * we moved the start of row one or two bytes in we will go one or two bytes |
459 | * past the end of the row. So the glyph cache needs to have 2 bytes of |
460 | * zero padding at the end of each row. This is the extra memory cost to |
461 | * accommodate this algorithm. |
462 | * |
463 | * The resulting text is perhaps fractionally better in overall perception |
464 | * than rounding to the whole pixel grid, as a few issues arise. |
465 | * |
466 | * * the improvement in inter-glyph spacing as well as being limited |
467 | * to 1/3 pixel resolution, is also limited because the glyphs were hinted |
468 | * so they fit to the whole pixel grid. It may be worthwhile to pursue |
469 | * disabling x-axis gridfitting. |
470 | * |
471 | * * an LCD display may have gaps between the pixels that are greater |
472 | * than the subpixels. Thus for thin stemmed fonts, if the shift causes |
473 | * the "heart" of a stem to span whole pixels it may appear more diffuse - |
474 | * less sharp. Eliminating hinting would probably not make this worse - in |
475 | * effect we have already doing that here. But it would improve the spacing. |
476 | * |
477 | * * perhaps contradicting the above point in some ways, more diffuse glyphs |
478 | * are better at reducing colour fringing, but what appears to be more |
479 | * colour fringing in this FM case is more likely attributable to a greater |
480 | * likelihood for glyphs to abutt. In integer metrics or even whole pixel |
481 | * rendered fractional metrics, there's typically more space between the |
482 | * glyphs. Perhaps disabling X-axis grid-fitting will help with that. |
483 | */ |
484 | GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist) { |
485 | |
486 | int g; |
487 | size_t bytesNeeded; |
488 | jlong *imagePtrs; |
489 | jfloat* positions = NULL; |
490 | GlyphInfo *ginfo; |
491 | GlyphBlitVector *gbv; |
492 | |
493 | jfloat x = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListX); |
494 | jfloat y = (*env)->GetFloatField(env, glyphlist, sunFontIDs.glyphListY); |
495 | jint len = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen); |
496 | jlongArray glyphImages = (jlongArray) |
497 | (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphImages); |
498 | jfloatArray glyphPositions = |
499 | (*env)->GetBooleanField(env, glyphlist, sunFontIDs.glyphListUsePos) |
500 | ? (jfloatArray) |
501 | (*env)->GetObjectField(env, glyphlist, sunFontIDs.glyphListPos) |
502 | : NULL; |
503 | jboolean subPixPos = |
504 | (*env)->GetBooleanField(env,glyphlist, sunFontIDs.lcdSubPixPos); |
505 | |
506 | bytesNeeded = sizeof(GlyphBlitVector)+sizeof(ImageRef)*len; |
507 | gbv = (GlyphBlitVector*)malloc(bytesNeeded); |
508 | if (gbv == NULL) { |
509 | return NULL; |
510 | } |
511 | gbv->numGlyphs = len; |
512 | gbv->glyphs = (ImageRef*)((unsigned char*)gbv+sizeof(GlyphBlitVector)); |
513 | |
514 | imagePtrs = (*env)->GetPrimitiveArrayCritical(env, glyphImages, NULL); |
515 | if (imagePtrs == NULL) { |
516 | free(gbv); |
517 | return (GlyphBlitVector*)NULL; |
518 | } |
519 | |
520 | /* The position of the start of the text is adjusted up so |
521 | * that we can round it to an integral pixel position for a |
522 | * bitmap glyph or non-subpixel positioning, and round it to an |
523 | * integral subpixel position for that case, hence 0.5/3 = 0.166667 |
524 | * Presently subPixPos means FM, and FM disables embedded bitmaps |
525 | * Therefore if subPixPos is true we should never get embedded bitmaps |
526 | * and the glyphlist will be homogenous. This test and the position |
527 | * adjustments will need to be per glyph once this case becomes |
528 | * heterogenous. |
529 | * Also set subPixPos=false if detect a B&W bitmap as we only |
530 | * need to test that on a per glyph basis once the list becomes |
531 | * heterogenous |
532 | */ |
533 | if (subPixPos && len > 0) { |
534 | ginfo = (GlyphInfo*)imagePtrs[0]; |
535 | /* rowBytes==width tests if its a B&W or LCD glyph */ |
536 | if (ginfo->width == ginfo->rowBytes) { |
537 | subPixPos = JNI_FALSE; |
538 | } |
539 | } |
540 | if (subPixPos) { |
541 | x += 0.1666667f; |
542 | y += 0.1666667f; |
543 | } else { |
544 | x += 0.5f; |
545 | y += 0.5f; |
546 | } |
547 | |
548 | if (glyphPositions) { |
549 | int n = -1; |
550 | |
551 | positions = |
552 | (*env)->GetPrimitiveArrayCritical(env, glyphPositions, NULL); |
553 | if (positions == NULL) { |
554 | (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, |
555 | imagePtrs, JNI_ABORT); |
556 | free(gbv); |
557 | return (GlyphBlitVector*)NULL; |
558 | } |
559 | |
560 | for (g=0; g<len; g++) { |
561 | jfloat px, py; |
562 | |
563 | ginfo = (GlyphInfo*)imagePtrs[g]; |
564 | gbv->glyphs[g].glyphInfo = ginfo; |
565 | gbv->glyphs[g].pixels = ginfo->image; |
566 | gbv->glyphs[g].width = ginfo->width; |
567 | gbv->glyphs[g].rowBytes = ginfo->rowBytes; |
568 | gbv->glyphs[g].height = ginfo->height; |
569 | |
570 | px = x + positions[++n]; |
571 | py = y + positions[++n]; |
572 | |
573 | /* |
574 | * Subpixel positioning may be requested for LCD text. |
575 | * |
576 | * Subpixel positioning can take place only in the direction in |
577 | * which the subpixels increase the resolution. |
578 | * So this is useful for the typical case of vertical stripes |
579 | * increasing the resolution in the direction of the glyph |
580 | * advances - ie typical horizontally laid out text. |
581 | * If the subpixel stripes are horizontal, subpixel positioning |
582 | * can take place only in the vertical direction, which isn't |
583 | * as useful - you would have to be drawing rotated text on |
584 | * a display which actually had that organisation. A pretty |
585 | * unlikely combination. |
586 | * So this is supported only for vertical stripes which |
587 | * increase the horizontal resolution. |
588 | * If in this case the client also rotates the text then there |
589 | * will still be some benefit for small rotations. For 90 degree |
590 | * rotation there's no horizontal advance and less benefit |
591 | * from the subpixel rendering too. |
592 | * The test for width==rowBytes detects the case where the glyph |
593 | * is a B&W image obtained from an embedded bitmap. In that |
594 | * case we cannot apply sub-pixel positioning so ignore it. |
595 | * This is handled on a per glyph basis. |
596 | */ |
597 | if (subPixPos) { |
598 | int frac; |
599 | float pos = px + ginfo->topLeftX; |
600 | FLOOR_ASSIGN(gbv->glyphs[g].x, pos); |
601 | /* Calculate the fractional pixel position - ie the subpixel |
602 | * position within the RGB/BGR triple. We are rounding to |
603 | * the nearest, even though we just do (int) since at the |
604 | * start of the loop the position was already adjusted by |
605 | * 0.5 (sub)pixels to get rounding. |
606 | * Thus the "fractional" position will be 0, 1 or 2. |
607 | * eg 0->0.32 is 0, 0.33->0.66 is 1, > 0.66->0.99 is 2. |
608 | * We can use an (int) cast here since the floor operation |
609 | * above guarantees us that the value is positive. |
610 | */ |
611 | frac = (int)((pos - gbv->glyphs[g].x)*3); |
612 | if (frac == 0) { |
613 | /* frac rounded down to zero, so this is equivalent |
614 | * to no sub-pixel positioning. |
615 | */ |
616 | gbv->glyphs[g].rowBytesOffset = 0; |
617 | } else { |
618 | /* In this case we need to adjust both the position at |
619 | * which the glyph will be positioned by one pixel to the |
620 | * left and adjust the position in the glyph image row |
621 | * from which to extract the data |
622 | * Every glyph image row has 2 bytes padding |
623 | * on the right to account for this. |
624 | */ |
625 | gbv->glyphs[g].rowBytesOffset = 3-frac; |
626 | gbv->glyphs[g].x += 1; |
627 | } |
628 | } else { |
629 | FLOOR_ASSIGN(gbv->glyphs[g].x, px + ginfo->topLeftX); |
630 | gbv->glyphs[g].rowBytesOffset = 0; |
631 | } |
632 | FLOOR_ASSIGN(gbv->glyphs[g].y, py + ginfo->topLeftY); |
633 | } |
634 | (*env)->ReleasePrimitiveArrayCritical(env,glyphPositions, |
635 | positions, JNI_ABORT); |
636 | } else { |
637 | for (g=0; g<len; g++) { |
638 | ginfo = (GlyphInfo*)imagePtrs[g]; |
639 | gbv->glyphs[g].glyphInfo = ginfo; |
640 | gbv->glyphs[g].pixels = ginfo->image; |
641 | gbv->glyphs[g].width = ginfo->width; |
642 | gbv->glyphs[g].rowBytes = ginfo->rowBytes; |
643 | gbv->glyphs[g].height = ginfo->height; |
644 | |
645 | if (subPixPos) { |
646 | int frac; |
647 | float pos = x + ginfo->topLeftX; |
648 | FLOOR_ASSIGN(gbv->glyphs[g].x, pos); |
649 | frac = (int)((pos - gbv->glyphs[g].x)*3); |
650 | if (frac == 0) { |
651 | gbv->glyphs[g].rowBytesOffset = 0; |
652 | } else { |
653 | gbv->glyphs[g].rowBytesOffset = 3-frac; |
654 | gbv->glyphs[g].x += 1; |
655 | } |
656 | } else { |
657 | FLOOR_ASSIGN(gbv->glyphs[g].x, x + ginfo->topLeftX); |
658 | gbv->glyphs[g].rowBytesOffset = 0; |
659 | } |
660 | FLOOR_ASSIGN(gbv->glyphs[g].y, y + ginfo->topLeftY); |
661 | /* copy image data into this array at x/y locations */ |
662 | x += ginfo->advanceX; |
663 | y += ginfo->advanceY; |
664 | } |
665 | } |
666 | |
667 | (*env)->ReleasePrimitiveArrayCritical(env, glyphImages, imagePtrs, |
668 | JNI_ABORT); |
669 | return gbv; |
670 | } |
671 | |
672 | /* LCD text needs to go through a gamma (contrast) adjustment. |
673 | * Gamma is constrained to the range 1.0->2.2 with a quantization of |
674 | * 0.01 (more than good enough). Representing as an integer with that |
675 | * precision yields a range 100->250 thus we need to store up to 151 LUTs |
676 | * and inverse LUTs. |
677 | * We allocate the actual LUTs on an as needed basis. Typically zero or |
678 | * one is what will be needed. |
679 | * Colour component values are in the range 0.0->1.0 represented as an integer |
680 | * in the range 0->255 (ie in a byte). It is assumed that even if we have 5 |
681 | * bit colour components these are presented mapped on to 8 bit components. |
682 | * lcdGammaLUT references LUTs which convert linear colour components |
683 | * to a gamma adjusted space, and |
684 | * lcdInvGammaLUT references LUTs which convert gamma adjusted colour |
685 | * components to a linear space. |
686 | */ |
687 | #define MIN_GAMMA 100 |
688 | #define MAX_GAMMA 250 |
689 | #define LCDLUTCOUNT (MAX_GAMMA-MIN_GAMMA+1) |
690 | UInt8 *lcdGammaLUT[LCDLUTCOUNT]; |
691 | UInt8 *lcdInvGammaLUT[LCDLUTCOUNT]; |
692 | |
693 | void initLUT(int gamma) { |
694 | int i,index; |
695 | double ig,g; |
696 | |
697 | index = gamma-MIN_GAMMA; |
698 | |
699 | lcdGammaLUT[index] = (UInt8*)malloc(256); |
700 | lcdInvGammaLUT[index] = (UInt8*)malloc(256); |
701 | if (gamma==100) { |
702 | for (i=0;i<256;i++) { |
703 | lcdGammaLUT[index][i] = (UInt8)i; |
704 | lcdInvGammaLUT[index][i] = (UInt8)i; |
705 | } |
706 | return; |
707 | } |
708 | |
709 | ig = ((double)gamma)/100.0; |
710 | g = 1.0/ig; |
711 | lcdGammaLUT[index][0] = (UInt8)0; |
712 | lcdInvGammaLUT[index][0] = (UInt8)0; |
713 | lcdGammaLUT[index][255] = (UInt8)255; |
714 | lcdInvGammaLUT[index][255] = (UInt8)255; |
715 | for (i=1;i<255;i++) { |
716 | double val = ((double)i)/255.0; |
717 | double gval = pow(val, g); |
718 | double igval = pow(val, ig); |
719 | lcdGammaLUT[index][i] = (UInt8)(255*gval); |
720 | lcdInvGammaLUT[index][i] = (UInt8)(255*igval); |
721 | } |
722 | } |
723 | |
724 | static unsigned char* getLCDGammaLUT(int gamma) { |
725 | int index; |
726 | |
727 | if (gamma<MIN_GAMMA) { |
728 | gamma = MIN_GAMMA; |
729 | } else if (gamma>MAX_GAMMA) { |
730 | gamma = MAX_GAMMA; |
731 | } |
732 | index = gamma-MIN_GAMMA; |
733 | if (!lcdGammaLUT[index]) { |
734 | initLUT(gamma); |
735 | } |
736 | return (unsigned char*)lcdGammaLUT[index]; |
737 | } |
738 | |
739 | static unsigned char* getInvLCDGammaLUT(int gamma) { |
740 | int index; |
741 | |
742 | if (gamma<MIN_GAMMA) { |
743 | gamma = MIN_GAMMA; |
744 | } else if (gamma>MAX_GAMMA) { |
745 | gamma = MAX_GAMMA; |
746 | } |
747 | index = gamma-MIN_GAMMA; |
748 | if (!lcdInvGammaLUT[index]) { |
749 | initLUT(gamma); |
750 | } |
751 | return (unsigned char*)lcdInvGammaLUT[index]; |
752 | } |
753 | |
754 | #if 0 |
755 | void printDefaultTables(int gamma) { |
756 | int i; |
757 | UInt8 *g, *ig; |
758 | lcdGammaLUT[gamma-MIN_GAMMA] = NULL; |
759 | lcdInvGammaLUT[gamma-MIN_GAMMA] = NULL; |
760 | g = getLCDGammaLUT(gamma); |
761 | ig = getInvLCDGammaLUT(gamma); |
762 | printf("UInt8 defaultGammaLUT[256] = {\n" ); |
763 | for (i=0;i<256;i++) { |
764 | if (i % 8 == 0) { |
765 | printf(" /* %3d */ " , i); |
766 | } |
767 | printf("%4d, " ,(int)(g[i]&0xff)); |
768 | if ((i+1) % 8 == 0) { |
769 | printf("\n" ); |
770 | } |
771 | } |
772 | printf("};\n" ); |
773 | |
774 | printf("UInt8 defaultInvGammaLUT[256] = {\n" ); |
775 | for (i=0;i<256;i++) { |
776 | if (i % 8 == 0) { |
777 | printf(" /* %3d */ " , i); |
778 | } |
779 | printf("%4d, " ,(int)(ig[i]&0xff)); |
780 | if ((i+1) % 8 == 0) { |
781 | printf("\n" ); |
782 | } |
783 | } |
784 | printf("};\n" ); |
785 | } |
786 | #endif |
787 | |
788 | /* These tables are generated for a Gamma adjustment of 1.4 */ |
789 | UInt8 defaultGammaLUT[256] = { |
790 | /* 0 */ 0, 4, 7, 10, 13, 15, 17, 19, |
791 | /* 8 */ 21, 23, 25, 27, 28, 30, 32, 33, |
792 | /* 16 */ 35, 36, 38, 39, 41, 42, 44, 45, |
793 | /* 24 */ 47, 48, 49, 51, 52, 53, 55, 56, |
794 | /* 32 */ 57, 59, 60, 61, 62, 64, 65, 66, |
795 | /* 40 */ 67, 69, 70, 71, 72, 73, 75, 76, |
796 | /* 48 */ 77, 78, 79, 80, 81, 83, 84, 85, |
797 | /* 56 */ 86, 87, 88, 89, 90, 91, 92, 93, |
798 | /* 64 */ 94, 96, 97, 98, 99, 100, 101, 102, |
799 | /* 72 */ 103, 104, 105, 106, 107, 108, 109, 110, |
800 | /* 80 */ 111, 112, 113, 114, 115, 116, 117, 118, |
801 | /* 88 */ 119, 120, 121, 122, 123, 124, 125, 125, |
802 | /* 96 */ 126, 127, 128, 129, 130, 131, 132, 133, |
803 | /* 104 */ 134, 135, 136, 137, 138, 138, 139, 140, |
804 | /* 112 */ 141, 142, 143, 144, 145, 146, 147, 147, |
805 | /* 120 */ 148, 149, 150, 151, 152, 153, 154, 154, |
806 | /* 128 */ 155, 156, 157, 158, 159, 160, 161, 161, |
807 | /* 136 */ 162, 163, 164, 165, 166, 167, 167, 168, |
808 | /* 144 */ 169, 170, 171, 172, 172, 173, 174, 175, |
809 | /* 152 */ 176, 177, 177, 178, 179, 180, 181, 181, |
810 | /* 160 */ 182, 183, 184, 185, 186, 186, 187, 188, |
811 | /* 168 */ 189, 190, 190, 191, 192, 193, 194, 194, |
812 | /* 176 */ 195, 196, 197, 198, 198, 199, 200, 201, |
813 | /* 184 */ 201, 202, 203, 204, 205, 205, 206, 207, |
814 | /* 192 */ 208, 208, 209, 210, 211, 212, 212, 213, |
815 | /* 200 */ 214, 215, 215, 216, 217, 218, 218, 219, |
816 | /* 208 */ 220, 221, 221, 222, 223, 224, 224, 225, |
817 | /* 216 */ 226, 227, 227, 228, 229, 230, 230, 231, |
818 | /* 224 */ 232, 233, 233, 234, 235, 236, 236, 237, |
819 | /* 232 */ 238, 239, 239, 240, 241, 242, 242, 243, |
820 | /* 240 */ 244, 244, 245, 246, 247, 247, 248, 249, |
821 | /* 248 */ 249, 250, 251, 252, 252, 253, 254, 255, |
822 | }; |
823 | |
824 | UInt8 defaultInvGammaLUT[256] = { |
825 | /* 0 */ 0, 0, 0, 0, 0, 1, 1, 1, |
826 | /* 8 */ 2, 2, 2, 3, 3, 3, 4, 4, |
827 | /* 16 */ 5, 5, 6, 6, 7, 7, 8, 8, |
828 | /* 24 */ 9, 9, 10, 10, 11, 12, 12, 13, |
829 | /* 32 */ 13, 14, 15, 15, 16, 17, 17, 18, |
830 | /* 40 */ 19, 19, 20, 21, 21, 22, 23, 23, |
831 | /* 48 */ 24, 25, 26, 26, 27, 28, 29, 29, |
832 | /* 56 */ 30, 31, 32, 32, 33, 34, 35, 36, |
833 | /* 64 */ 36, 37, 38, 39, 40, 40, 41, 42, |
834 | /* 72 */ 43, 44, 45, 45, 46, 47, 48, 49, |
835 | /* 80 */ 50, 51, 52, 52, 53, 54, 55, 56, |
836 | /* 88 */ 57, 58, 59, 60, 61, 62, 63, 64, |
837 | /* 96 */ 64, 65, 66, 67, 68, 69, 70, 71, |
838 | /* 104 */ 72, 73, 74, 75, 76, 77, 78, 79, |
839 | /* 112 */ 80, 81, 82, 83, 84, 85, 86, 87, |
840 | /* 120 */ 88, 89, 90, 91, 92, 93, 95, 96, |
841 | /* 128 */ 97, 98, 99, 100, 101, 102, 103, 104, |
842 | /* 136 */ 105, 106, 107, 109, 110, 111, 112, 113, |
843 | /* 144 */ 114, 115, 116, 117, 119, 120, 121, 122, |
844 | /* 152 */ 123, 124, 125, 127, 128, 129, 130, 131, |
845 | /* 160 */ 132, 133, 135, 136, 137, 138, 139, 140, |
846 | /* 168 */ 142, 143, 144, 145, 146, 148, 149, 150, |
847 | /* 176 */ 151, 152, 154, 155, 156, 157, 159, 160, |
848 | /* 184 */ 161, 162, 163, 165, 166, 167, 168, 170, |
849 | /* 192 */ 171, 172, 173, 175, 176, 177, 178, 180, |
850 | /* 200 */ 181, 182, 184, 185, 186, 187, 189, 190, |
851 | /* 208 */ 191, 193, 194, 195, 196, 198, 199, 200, |
852 | /* 216 */ 202, 203, 204, 206, 207, 208, 210, 211, |
853 | /* 224 */ 212, 214, 215, 216, 218, 219, 220, 222, |
854 | /* 232 */ 223, 224, 226, 227, 228, 230, 231, 232, |
855 | /* 240 */ 234, 235, 236, 238, 239, 241, 242, 243, |
856 | /* 248 */ 245, 246, 248, 249, 250, 252, 253, 255, |
857 | }; |
858 | |
859 | |
860 | /* Since our default is 140, here we can populate that from pre-calculated |
861 | * data, it needs only 512 bytes - plus a few more of overhead - and saves |
862 | * about that many intrinsic function calls plus other FP calculations. |
863 | */ |
864 | void initLCDGammaTables() { |
865 | memset(lcdGammaLUT, 0, LCDLUTCOUNT * sizeof(UInt8*)); |
866 | memset(lcdInvGammaLUT, 0, LCDLUTCOUNT * sizeof(UInt8*)); |
867 | /* printDefaultTables(140); */ |
868 | lcdGammaLUT[40] = defaultGammaLUT; |
869 | lcdInvGammaLUT[40] = defaultInvGammaLUT; |
870 | } |
871 | |