1 /*
2  * Copyright (c) 2007, 2018, 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 package sun.java2d.marlin;
27 
28 import jdk.internal.misc.Unsafe;
29 
30 /**
31  * An object used to cache pre-rendered complex paths.
32  *
33  * @see Renderer
34  */
35 public final class MarlinCache implements MarlinConst {
36 
37     static final boolean FORCE_RLE = MarlinProperties.isForceRLE();
38     static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE();
39     // minimum width to try using RLE encoding:
40     static final int RLE_MIN_WIDTH
41         = Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth());
42     // maximum width for RLE encoding:
43     // values are stored as int [x|alpha] where alpha is 8 bits
44     static final int RLE_MAX_WIDTH = 1 << (24 - 1);
45 
46     // 4096 (pixels) alpha values (width) x 64 rows / 4 (tile) = 64K bytes
47     // x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression
48     static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_WIDTH >> 2; // 64K
49 
50     // The alpha map used by this object (taken out of our map cache) to convert
51     // pixel coverage counts gotten from MarlinCache (which are in the range
52     // [0, maxalpha]) into alpha values, which are in [0,256).
53     static final byte[] ALPHA_MAP;
54 
55     static final OffHeapArray ALPHA_MAP_UNSAFE;
56 
57     static {
58         final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA);
59 
60         ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K
61         ALPHA_MAP =_ALPHA_MAP;
62 
63         final Unsafe _unsafe = OffHeapArray.UNSAFE;
64         final long addr = ALPHA_MAP_UNSAFE.address;
65 
66         for (int i = 0; i < _ALPHA_MAP.length; i++) {
67             _unsafe.putByte(addr + i, _ALPHA_MAP[i]);
68         }
69     }
70 
71     int bboxX0, bboxY0, bboxX1, bboxY1;
72 
73     // 1D dirty arrays
74     // row index in rowAAChunk[]
75     final long[] rowAAChunkIndex = new long[TILE_H];
76     // first pixel (inclusive) for each row
77     final int[] rowAAx0 = new int[TILE_H];
78     // last pixel (exclusive) for each row
79     final int[] rowAAx1 = new int[TILE_H];
80     // encoding mode (0=raw, 1=RLE encoding) for each row
81     final int[] rowAAEnc = new int[TILE_H];
82     // coded length (RLE encoding) for each row
83     final long[] rowAALen = new long[TILE_H];
84     // last position in RLE decoding for each row (getAlpha):
85     final long[] rowAAPos = new long[TILE_H];
86 
87     // dirty off-heap array containing pixel coverages for (32) rows (packed)
88     // if encoding=raw, it contains alpha coverage values (val) as integer
89     // if encoding=RLE, it contains tuples (val, last x-coordinate exclusive)
90     // use rowAAx0/rowAAx1 to get row indices within this chunk
91     final OffHeapArray rowAAChunk;
92 
93     // current position in rowAAChunk array
94     long rowAAChunkPos;
95 
96     // touchedTile[i] is the sum of all the alphas in the tile with
97     // x=j*TILE_SIZE+bboxX0.
98     int[] touchedTile;
99 
100     // per-thread renderer stats
101     final RendererStats rdrStats;
102 
103     // touchedTile ref (clean)
104     private final IntArrayCache.Reference touchedTile_ref;
105 
106     int tileMin, tileMax;
107 
108     boolean useRLE = false;
109 
MarlinCache(final IRendererContext rdrCtx)110     MarlinCache(final IRendererContext rdrCtx) {
111         this.rdrStats = rdrCtx.stats();
112 
113         rowAAChunk = rdrCtx.newOffHeapArray(INITIAL_CHUNK_ARRAY); // 64K
114 
115         touchedTile_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line
116         touchedTile     = touchedTile_ref.initial;
117 
118         // tile used marks:
119         tileMin = Integer.MAX_VALUE;
120         tileMax = Integer.MIN_VALUE;
121     }
122 
init(int minx, int miny, int maxx, int maxy)123     void init(int minx, int miny, int maxx, int maxy)
124     {
125         // assert maxy >= miny && maxx >= minx;
126         bboxX0 = minx;
127         bboxY0 = miny;
128         bboxX1 = maxx;
129         bboxY1 = maxy;
130 
131         final int width = (maxx - minx);
132 
133         if (FORCE_NO_RLE) {
134             useRLE = false;
135         } else if (FORCE_RLE) {
136             useRLE = true;
137         } else {
138             // heuristics: use both bbox area and complexity
139             // ie number of primitives:
140 
141             // fast check min and max width (maxx < 23bits):
142             useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH);
143         }
144 
145         // the ceiling of (maxy - miny + 1) / TILE_SIZE;
146         final int nxTiles = (width + TILE_W) >> TILE_W_LG;
147 
148         if (nxTiles > INITIAL_ARRAY) {
149             if (DO_STATS) {
150                 rdrStats.stat_array_marlincache_touchedTile.add(nxTiles);
151             }
152             touchedTile = touchedTile_ref.getArray(nxTiles);
153         }
154     }
155 
156     /**
157      * Disposes this cache:
158      * clean up before reusing this instance
159      */
dispose()160     void dispose() {
161         // Reset touchedTile if needed:
162         resetTileLine(0);
163 
164         if (DO_STATS) {
165             rdrStats.totalOffHeap += rowAAChunk.length;
166         }
167 
168         // Return arrays:
169         touchedTile = touchedTile_ref.putArray(touchedTile, 0, 0); // already zero filled
170 
171         // At last: resize back off-heap rowAA to initial size
172         if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) {
173             // note: may throw OOME:
174             rowAAChunk.resize(INITIAL_CHUNK_ARRAY);
175         }
176         if (DO_CLEAN_DIRTY) {
177             // Force zero-fill dirty arrays:
178             rowAAChunk.fill(BYTE_0);
179         }
180     }
181 
resetTileLine(final int pminY)182     void resetTileLine(final int pminY) {
183         // update bboxY0 to process a complete tile line [0 - 32]
184         bboxY0 = pminY;
185 
186         // reset current pos
187         if (DO_STATS) {
188             rdrStats.stat_cache_rowAAChunk.add(rowAAChunkPos);
189         }
190         rowAAChunkPos = 0L;
191 
192         // Reset touchedTile:
193         if (tileMin != Integer.MAX_VALUE) {
194             if (DO_STATS) {
195                 rdrStats.stat_cache_tiles.add(tileMax - tileMin);
196             }
197             // clean only dirty touchedTile:
198             if (tileMax == 1) {
199                 touchedTile[0] = 0;
200             } else {
201                 IntArrayCache.fill(touchedTile, tileMin, tileMax, 0);
202             }
203             // reset tile used marks:
204             tileMin = Integer.MAX_VALUE;
205             tileMax = Integer.MIN_VALUE;
206         }
207 
208         if (DO_CLEAN_DIRTY) {
209             // Force zero-fill dirty arrays:
210             rowAAChunk.fill(BYTE_0);
211         }
212     }
213 
clearAARow(final int y)214     void clearAARow(final int y) {
215         // process tile line [0 - 32]
216         final int row = y - bboxY0;
217 
218         // update pixel range:
219         rowAAx0[row]  = 0; // first pixel inclusive
220         rowAAx1[row]  = 0; //  last pixel exclusive
221         rowAAEnc[row] = 0; // raw encoding
222 
223         // note: leave rowAAChunkIndex[row] undefined
224         // and rowAALen[row] & rowAAPos[row] (RLE)
225     }
226 
227     /**
228      * Copy the given alpha data into the rowAA cache
229      * @param alphaRow alpha data to copy from
230      * @param y y pixel coordinate
231      * @param px0 first pixel inclusive x0
232      * @param px1 last pixel exclusive x1
233      */
copyAARowNoRLE(final int[] alphaRow, final int y, final int px0, final int px1)234     void copyAARowNoRLE(final int[] alphaRow, final int y,
235                    final int px0, final int px1)
236     {
237         // skip useless pixels above boundary
238         final int px_bbox1 = FloatMath.min(px1, bboxX1);
239 
240         if (DO_LOG_BOUNDS) {
241             MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
242                                 + " (" + px1 + ") [ for y=" + y);
243         }
244 
245         final int row = y - bboxY0;
246 
247         // update pixel range:
248         rowAAx0[row]  = px0;      // first pixel inclusive
249         rowAAx1[row]  = px_bbox1; //  last pixel exclusive
250         rowAAEnc[row] = 0; // raw encoding
251 
252         // get current position (bytes):
253         final long pos = rowAAChunkPos;
254         // update row index to current position:
255         rowAAChunkIndex[row] = pos;
256 
257         // determine need array size:
258         // for RLE encoding, position must be aligned to 4 bytes (int):
259         // align - 1 = 3 so add +3 and round-off by mask ~3 = -4
260         final long needSize = pos + ((px_bbox1 - px0 + 3) & -4);
261 
262         // update next position (bytes):
263         rowAAChunkPos = needSize;
264 
265         // update row data:
266         final OffHeapArray _rowAAChunk = rowAAChunk;
267         // ensure rowAAChunk capacity:
268         if (_rowAAChunk.length < needSize) {
269             expandRowAAChunk(needSize);
270         }
271         if (DO_STATS) {
272             rdrStats.stat_cache_rowAA.add(px_bbox1 - px0);
273         }
274 
275         // rowAA contains only alpha values for range[x0; x1[
276         final int[] _touchedTile = touchedTile;
277         final int _TILE_SIZE_LG = TILE_W_LG;
278 
279         final int from = px0      - bboxX0; // first pixel inclusive
280         final int to   = px_bbox1 - bboxX0; //  last pixel exclusive
281 
282         final Unsafe _unsafe = OffHeapArray.UNSAFE;
283         final long SIZE_BYTE = 1L;
284         final long addr_alpha = ALPHA_MAP_UNSAFE.address;
285         long addr_off = _rowAAChunk.address + pos;
286 
287         // compute alpha sum into rowAA:
288         for (int x = from, val = 0; x < to; x++) {
289             // alphaRow is in [0; MAX_COVERAGE]
290             val += alphaRow[x]; // [from; to[
291 
292             // ensure values are in [0; MAX_AA_ALPHA] range
293             if (DO_AA_RANGE_CHECK) {
294                 if (val < 0) {
295                     MarlinUtils.logInfo("Invalid coverage = " + val);
296                     val = 0;
297                 }
298                 if (val > MAX_AA_ALPHA) {
299                     MarlinUtils.logInfo("Invalid coverage = " + val);
300                     val = MAX_AA_ALPHA;
301                 }
302             }
303 
304             // store alpha sum (as byte):
305             if (val == 0) {
306                 _unsafe.putByte(addr_off, (byte)0); // [0-255]
307             } else {
308                 _unsafe.putByte(addr_off, _unsafe.getByte(addr_alpha + val)); // [0-255]
309 
310                 // update touchedTile
311                 _touchedTile[x >> _TILE_SIZE_LG] += val;
312             }
313             addr_off += SIZE_BYTE;
314         }
315 
316         // update tile used marks:
317         int tx = from >> _TILE_SIZE_LG; // inclusive
318         if (tx < tileMin) {
319             tileMin = tx;
320         }
321 
322         tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
323         if (tx > tileMax) {
324             tileMax = tx;
325         }
326 
327         if (DO_LOG_BOUNDS) {
328             MarlinUtils.logInfo("clear = [" + from + " ... " + to + "[");
329         }
330 
331         // Clear alpha row for reuse:
332         IntArrayCache.fill(alphaRow, from, px1 + 1 - bboxX0, 0);
333     }
334 
copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow, final int y, final int px0, final int px1)335     void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow,
336                       final int y, final int px0, final int px1)
337     {
338         // Copy rowAA data into the piscesCache if one is present
339         final int _bboxX0 = bboxX0;
340 
341         // process tile line [0 - 32]
342         final int row  =   y -  bboxY0;
343         final int from = px0 - _bboxX0; // first pixel inclusive
344 
345         // skip useless pixels above boundary
346         final int px_bbox1 = FloatMath.min(px1, bboxX1);
347         final int to       = px_bbox1 - _bboxX0; //  last pixel exclusive
348 
349         if (DO_LOG_BOUNDS) {
350             MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
351                                 + " (" + px1 + ") [ for y=" + y);
352         }
353 
354         // get current position:
355         final long initialPos = startRLERow(row, px0, px_bbox1);
356 
357         // determine need array size:
358         // pessimistic: max needed size = deltaX x 4 (1 int)
359         final long needSize = initialPos + ((to - from) << 2);
360 
361         // update row data:
362         OffHeapArray _rowAAChunk = rowAAChunk;
363         // ensure rowAAChunk capacity:
364         if (_rowAAChunk.length < needSize) {
365             expandRowAAChunk(needSize);
366         }
367 
368         final Unsafe _unsafe = OffHeapArray.UNSAFE;
369         final long SIZE_INT = 4L;
370         final long addr_alpha = ALPHA_MAP_UNSAFE.address;
371         long addr_off = _rowAAChunk.address + initialPos;
372 
373         final int[] _touchedTile = touchedTile;
374         final int _TILE_SIZE_LG = TILE_W_LG;
375         final int _BLK_SIZE_LG  = BLOCK_SIZE_LG;
376 
377         // traverse flagged blocks:
378         final int blkW = (from >> _BLK_SIZE_LG);
379         final int blkE = (to   >> _BLK_SIZE_LG) + 1;
380         // ensure last block flag = 0 to process final block:
381         blkFlags[blkE] = 0;
382 
383         // Perform run-length encoding and store results in the piscesCache
384         int val = 0;
385         int cx0 = from;
386         int runLen;
387 
388         final int _MAX_VALUE = Integer.MAX_VALUE;
389         int last_t0 = _MAX_VALUE;
390 
391         int skip = 0;
392 
393         for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
394             if (blkFlags[t] != 0) {
395                 blkFlags[t] = 0;
396 
397                 if (last_t0 == _MAX_VALUE) {
398                     last_t0 = t;
399                 }
400                 continue;
401             }
402             if (last_t0 != _MAX_VALUE) {
403                 // emit blocks:
404                 blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from);
405                 last_t0 = _MAX_VALUE;
406 
407                 // (last block pixel+1) inclusive => +1
408                 blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, to);
409 
410                 for (cx = blk_x0; cx < blk_x1; cx++) {
411                     if ((delta = alphaRow[cx]) != 0) {
412                         alphaRow[cx] = 0;
413 
414                         // not first rle entry:
415                         if (cx != cx0) {
416                             runLen = cx - cx0;
417 
418                             // store alpha coverage (ensure within bounds):
419                             // as [absX|val] where:
420                             // absX is the absolute x-coordinate:
421                             // note: last pixel exclusive (>= 0)
422                             // note: it should check X is smaller than 23bits (overflow)!
423 
424                             // check address alignment to 4 bytes:
425                             if (DO_CHECK_UNSAFE) {
426                                 if ((addr_off & 3) != 0) {
427                                     MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off);
428                                 }
429                             }
430 
431                             // special case to encode entries into a single int:
432                             if (val == 0) {
433                                 _unsafe.putInt(addr_off,
434                                     ((_bboxX0 + cx) << 8)
435                                 );
436                             } else {
437                                 _unsafe.putInt(addr_off,
438                                     ((_bboxX0 + cx) << 8)
439                                     | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255]
440                                 );
441 
442                                 if (runLen == 1) {
443                                     _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
444                                 } else {
445                                     touchTile(cx0, val, cx, runLen, _touchedTile);
446                                 }
447                             }
448                             addr_off += SIZE_INT;
449 
450                             if (DO_STATS) {
451                                 rdrStats.hist_tile_generator_encoding_runLen
452                                     .add(runLen);
453                             }
454                             cx0 = cx;
455                         }
456 
457                         // alpha value = running sum of coverage delta:
458                         val += delta;
459 
460                         // ensure values are in [0; MAX_AA_ALPHA] range
461                         if (DO_AA_RANGE_CHECK) {
462                             if (val < 0) {
463                                 MarlinUtils.logInfo("Invalid coverage = " + val);
464                                 val = 0;
465                             }
466                             if (val > MAX_AA_ALPHA) {
467                                 MarlinUtils.logInfo("Invalid coverage = " + val);
468                                 val = MAX_AA_ALPHA;
469                             }
470                         }
471                     }
472                 }
473             } else if (DO_STATS) {
474                 skip++;
475             }
476         }
477 
478         // Process remaining RLE run:
479         runLen = to - cx0;
480 
481         // store alpha coverage (ensure within bounds):
482         // as (int)[absX|val] where:
483         // absX is the absolute x-coordinate in bits 31 to 8 and val in bits 0..7
484         // note: last pixel exclusive (>= 0)
485         // note: it should check X is smaller than 23bits (overflow)!
486 
487         // check address alignment to 4 bytes:
488         if (DO_CHECK_UNSAFE) {
489             if ((addr_off & 3) != 0) {
490                 MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off);
491             }
492         }
493 
494         // special case to encode entries into a single int:
495         if (val == 0) {
496             _unsafe.putInt(addr_off,
497                 ((_bboxX0 + to) << 8)
498             );
499         } else {
500             _unsafe.putInt(addr_off,
501                 ((_bboxX0 + to) << 8)
502                 | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255]
503             );
504 
505             if (runLen == 1) {
506                 _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
507             } else {
508                 touchTile(cx0, val, to, runLen, _touchedTile);
509             }
510         }
511         addr_off += SIZE_INT;
512 
513         if (DO_STATS) {
514             rdrStats.hist_tile_generator_encoding_runLen.add(runLen);
515         }
516 
517         long len = (addr_off - _rowAAChunk.address);
518 
519         // update coded length as bytes:
520         rowAALen[row] = (len - initialPos);
521 
522         // update current position:
523         rowAAChunkPos = len;
524 
525         if (DO_STATS) {
526             rdrStats.stat_cache_rowAA.add(rowAALen[row]);
527             rdrStats.hist_tile_generator_encoding_ratio.add(
528                 (100 * skip) / (blkE - blkW)
529             );
530         }
531 
532         // update tile used marks:
533         int tx = from >> _TILE_SIZE_LG; // inclusive
534         if (tx < tileMin) {
535             tileMin = tx;
536         }
537 
538         tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
539         if (tx > tileMax) {
540             tileMax = tx;
541         }
542 
543         // Clear alpha row for reuse:
544         alphaRow[to] = 0;
545         if (DO_CHECKS) {
546             IntArrayCache.check(blkFlags, blkW, blkE, 0);
547             IntArrayCache.check(alphaRow, from, px1 + 1 - bboxX0, 0);
548         }
549     }
550 
startRLERow(final int row, final int x0, final int x1)551     long startRLERow(final int row, final int x0, final int x1) {
552         // rows are supposed to be added by increasing y.
553         rowAAx0[row]  = x0; // first pixel inclusive
554         rowAAx1[row]  = x1; // last pixel exclusive
555         rowAAEnc[row] = 1; // RLE encoding
556         rowAAPos[row] = 0L; // position = 0
557 
558         // update row index to current position:
559         return (rowAAChunkIndex[row] = rowAAChunkPos);
560     }
561 
expandRowAAChunk(final long needSize)562     private void expandRowAAChunk(final long needSize) {
563         if (DO_STATS) {
564             rdrStats.stat_array_marlincache_rowAAChunk.add(needSize);
565         }
566 
567         // note: throw IOOB if neededSize > 2Gb:
568         final long newSize = ArrayCacheConst.getNewLargeSize(rowAAChunk.length,
569                                                              needSize);
570 
571         rowAAChunk.resize(newSize);
572     }
573 
touchTile(final int x0, final int val, final int x1, final int runLen, final int[] _touchedTile)574     private void touchTile(final int x0, final int val, final int x1,
575                            final int runLen,
576                            final int[] _touchedTile)
577     {
578         // the x and y of the current row, minus bboxX0, bboxY0
579         // process tile line [0 - 32]
580         final int _TILE_SIZE_LG = TILE_W_LG;
581 
582         // update touchedTile
583         int tx = (x0 >> _TILE_SIZE_LG);
584 
585         // handle trivial case: same tile (x0, x0+runLen)
586         if (tx == (x1 >> _TILE_SIZE_LG)) {
587             // same tile:
588             _touchedTile[tx] += val * runLen;
589             return;
590         }
591 
592         final int tx1 = (x1 - 1) >> _TILE_SIZE_LG;
593 
594         if (tx <= tx1) {
595             final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
596             _touchedTile[tx++] += val * (nextTileXCoord - x0);
597         }
598         if (tx < tx1) {
599             // don't go all the way to tx1 - we need to handle the last
600             // tile as a special case (just like we did with the first
601             final int tileVal = (val << _TILE_SIZE_LG);
602             for (; tx < tx1; tx++) {
603                 _touchedTile[tx] += tileVal;
604             }
605         }
606         // they will be equal unless x0 >> TILE_SIZE_LG == tx1
607         if (tx == tx1) {
608             final int txXCoord       =  tx      << _TILE_SIZE_LG;
609             final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
610 
611             final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1;
612             _touchedTile[tx] += val * (lastXCoord - txXCoord);
613         }
614     }
615 
alphaSumInTile(final int x)616     int alphaSumInTile(final int x) {
617         return touchedTile[(x - bboxX0) >> TILE_W_LG];
618     }
619 
620     @Override
toString()621     public String toString() {
622         return "bbox = ["
623             + bboxX0 + ", " + bboxY0 + " => "
624             + bboxX1 + ", " + bboxY1 + "]\n";
625     }
626 
buildAlphaMap(final int maxalpha)627     private static byte[] buildAlphaMap(final int maxalpha) {
628         // double size !
629         final byte[] alMap = new byte[maxalpha << 1];
630         final int halfmaxalpha = maxalpha >> 2;
631         for (int i = 0; i <= maxalpha; i++) {
632             alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
633         }
634         return alMap;
635     }
636 }
637