/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* 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 com.sun.marlin;
import com.sun.prism.impl.shape.MaskData;
import java.nio.ByteBuffer;
import java.util.Arrays;
import sun.misc.Unsafe;
public final class MaskMarlinAlphaConsumer implements MarlinAlphaConsumer {
int x, y, width, height;
final byte alphas[];
final ByteBuffer alphabuffer;
final MaskData maskdata = new MaskData();
boolean useFastFill;
int fastFillThreshold;
public MaskMarlinAlphaConsumer(int alphalen) {
this.alphas = new byte[alphalen];
alphabuffer = ByteBuffer.wrap(alphas);
}
public void setBoundsNoClone(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
maskdata.update(alphabuffer, x, y, w, h);
useFastFill = (w >= 32);
if (useFastFill) {
fastFillThreshold = (w >= 128) ? (w >> 1) : (w >> 2);
}
}
@Override
public int getOriginX() {
return x;
}
@Override
public int getOriginY() {
return y;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public int getAlphaLength() {
return alphas.length;
}
public MaskData getMaskData() {
return maskdata;
}
OffHeapArray ALPHA_MAP_USED = null;
@Override
public void setMaxAlpha(int maxalpha) {
ALPHA_MAP_USED = (maxalpha == 1) ? ALPHA_MAP_UNSAFE_NO_AA : ALPHA_MAP_UNSAFE;
}
// The alpha map used by this object (taken out of our map cache) to convert
// pixel coverage counts (which are in the range [0, maxalpha])
// into alpha values, which are in [0,255]).
static final byte[] ALPHA_MAP;
static final OffHeapArray ALPHA_MAP_UNSAFE;
static final byte[] ALPHA_MAP_NO_AA;
static final OffHeapArray ALPHA_MAP_UNSAFE_NO_AA;
static {
final Unsafe _unsafe = OffHeapArray.UNSAFE;
// AA:
byte[] _ALPHA_MAP = buildAlphaMap(MarlinConst.MAX_AA_ALPHA);
ALPHA_MAP = _ALPHA_MAP; // Keep alive the OffHeapArray
ALPHA_MAP_UNSAFE = new OffHeapArray(ALPHA_MAP, ALPHA_MAP.length); // 1K
long addr = ALPHA_MAP_UNSAFE.address;
for (int i = 0; i < _ALPHA_MAP.length; i++) {
_unsafe.putByte(addr + i, _ALPHA_MAP[i]);
}
// NoAA:
byte[] _ALPHA_MAP_NO_AA = buildAlphaMap(1);
ALPHA_MAP_NO_AA = _ALPHA_MAP_NO_AA; // Keep alive the OffHeapArray
ALPHA_MAP_UNSAFE_NO_AA = new OffHeapArray(ALPHA_MAP_NO_AA, ALPHA_MAP_NO_AA.length);
addr = ALPHA_MAP_UNSAFE_NO_AA.address;
for (int i = 0; i < _ALPHA_MAP_NO_AA.length; i++) {
_unsafe.putByte(addr + i, _ALPHA_MAP_NO_AA[i]);
}
}
private static byte[] buildAlphaMap(final int maxalpha) {
final byte[] alMap = new byte[maxalpha << 1];
final int halfmaxalpha = maxalpha >> 2;
for (int i = 0; i <= maxalpha; i++) {
alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
// System.out.println("alphaMap[" + i + "] = "
// + Byte.toUnsignedInt(alMap[i]));
}
return alMap;
}
@Override
public boolean supportBlockFlags() {
return true;
}
@Override
public void clearAlphas(final int pix_y) {
final int w = width;
final int off = (pix_y - y) * w;
// Clear complete row:
Arrays.fill(this.alphas, off, off + w, (byte)0);
}
@Override
public void setAndClearRelativeAlphas(final int[] alphaDeltas, final int pix_y,
final int pix_from, final int pix_to)
{
// System.out.println("setting row "+(pix_y - y)+
// " out of "+width+" x "+height);
final byte[] out = this.alphas;
final int w = width;
final int off = (pix_y - y) * w;
final Unsafe _unsafe = OffHeapArray.UNSAFE;
final long addr_alpha = ALPHA_MAP_USED.address;
final int from = pix_from - x;
// skip useless pixels above boundary
final int to = pix_to - x;
final int ato = Math.min(to, width);
// fast fill ?
final boolean fast = useFastFill && ((ato - from) < fastFillThreshold);
if (fast) {
// Zero-fill complete row:
Arrays.fill(out, off, off + w, (byte) 0);
int i = from;
int curAlpha = 0;
while (i < ato) {
curAlpha += alphaDeltas[i];
out[off + i] = _unsafe.getByte(addr_alpha + curAlpha); // [0..255]
i++;
}
} else {
int i = 0;
while (i < from) {
out[off + i] = 0;
i++;
}
int curAlpha = 0;
while (i < ato) {
curAlpha += alphaDeltas[i];
out[off + i] = _unsafe.getByte(addr_alpha + curAlpha); // [0..255]
i++;
}
while (i < w) {
out[off + i] = 0;
i++;
}
}
// Clear alpha row for reuse:
IntArrayCache.fill(alphaDeltas, from, to + 1, 0);
}
@Override
public void setAndClearRelativeAlphas(final int[] blkFlags, final int[] alphaDeltas, final int pix_y,
final int pix_from, final int pix_to)
{
// System.out.println("setting row "+(pix_y - y)+
// " out of "+width+" x "+height);
final byte[] out = this.alphas;
final int w = width;
final int off = (pix_y - y) * w;
final Unsafe _unsafe = OffHeapArray.UNSAFE;
final long addr_alpha = ALPHA_MAP_USED.address;
final int from = pix_from - x;
// skip useless pixels above boundary
final int to = pix_to - x;
final int ato = Math.min(to, width);
// fast fill ?
final boolean fast = useFastFill && ((ato - from) < fastFillThreshold);
final int _BLK_SIZE_LG = MarlinConst.BLOCK_SIZE_LG;
// traverse flagged blocks:
final int blkW = (from >> _BLK_SIZE_LG);
final int blkE = (ato >> _BLK_SIZE_LG) + 1;
// ensure last block flag = 0 to process final block:
blkFlags[blkE] = 0;
// Perform run-length encoding and store results in the piscesCache
int curAlpha = 0;
final int _MAX_VALUE = Integer.MAX_VALUE;
int last_t0 = _MAX_VALUE;
byte val;
if (fast) {
int i = from;
// Zero-fill complete row:
Arrays.fill(out, off, off + w, (byte) 0);
for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
if (blkFlags[t] != 0) {
blkFlags[t] = 0;
if (last_t0 == _MAX_VALUE) {
last_t0 = t;
}
continue;
}
if (last_t0 != _MAX_VALUE) {
// emit blocks:
blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from);
last_t0 = _MAX_VALUE;
// (last block pixel+1) inclusive => +1
blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, ato);
for (cx = blk_x0; cx < blk_x1; cx++) {
if ((delta = alphaDeltas[cx]) != 0) {
alphaDeltas[cx] = 0;
// fill span:
if (cx != i) {
// skip alpha = 0
if (curAlpha == 0) {
i = cx;
} else {
val = _unsafe.getByte(addr_alpha + curAlpha);
do {
out[off + i] = val;
i++;
} while (i < cx);
}
}
// alpha value = running sum of coverage delta:
curAlpha += delta;
}
}
}
}
// Process remaining span:
if (curAlpha != 0) {
val = _unsafe.getByte(addr_alpha + curAlpha);
while (i < ato) {
out[off + i] = val;
i++;
}
}
} else {
int i = 0;
while (i < from) {
out[off + i] = 0;
i++;
}
for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
if (blkFlags[t] != 0) {
blkFlags[t] = 0;
if (last_t0 == _MAX_VALUE) {
last_t0 = t;
}
continue;
}
if (last_t0 != _MAX_VALUE) {
// emit blocks:
blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from);
last_t0 = _MAX_VALUE;
// (last block pixel+1) inclusive => +1
blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, ato);
for (cx = blk_x0; cx < blk_x1; cx++) {
if ((delta = alphaDeltas[cx]) != 0) {
alphaDeltas[cx] = 0;
// fill span:
if (cx != i) {
val = _unsafe.getByte(addr_alpha + curAlpha);
do {
out[off + i] = val;
i++;
} while (i < cx);
}
// alpha value = running sum of coverage delta:
curAlpha += delta;
}
}
}
}
// Process remaining span:
if (curAlpha != 0) {
val = _unsafe.getByte(addr_alpha + curAlpha);
while (i < ato) {
out[off + i] = val;
i++;
}
}
while (i < w) {
out[off + i] = 0;
i++;
}
}
// Clear alpha row for reuse:
alphaDeltas[ato] = 0;
if (MarlinConst.DO_CHECKS) {
IntArrayCache.check(blkFlags, blkW, blkE, 0);
IntArrayCache.check(alphaDeltas, from, to + 1, 0);
}
}
}