/*
ChibiPaint
Copyright (c) 2006-2008 Marc Schefer
This file is part of ChibiPaint.
ChibiPaint is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ChibiPaint 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 for more details.
You should have received a copy of the GNU General Public License
along with ChibiPaint. If not, see <http://www.gnu.org/licenses/>.
*/
package com.chibipaint.engine;
import java.util.*;
import com.chibipaint.util.*;
public class CPLayer extends CPColorBmp {
public int blendMode = LM_NORMAL, alpha = 100;
public String name;
public boolean visible = true;
public final static int LM_NORMAL = 0;
public final static int LM_MULTIPLY = 1;
public final static int LM_ADD = 2;
public final static int LM_SCREEN = 3;
public final static int LM_LIGHTEN = 4;
public final static int LM_DARKEN = 5;
public final static int LM_SUBTRACT = 6;
public final static int LM_DODGE = 7;
public final static int LM_BURN = 8;
public final static int LM_OVERLAY = 9;
public final static int LM_HARDLIGHT = 10;
public final static int LM_SOFTLIGHT = 11;
public final static int LM_VIVIDLIGHT = 12;
public final static int LM_LINEARLIGHT = 13;
public final static int LM_PINLIGHT = 14;
//
// Look-Up tables for some of the blend modes
//
private static int softLightLUTSquare[];
private static int softLightLUTSquareRoot[];
public CPLayer(int width, int height) {
super(width, height);
name = new String("");
clear(0xffffff);
if (softLightLUTSquare == null)
makeLookUpTables();
}
public void makeLookUpTables() {
// V - V^2 table
softLightLUTSquare = new int[256];
for (int i = 0; i < 256; i++) {
double v = i / 255.;
softLightLUTSquare[i] = (int) ((v - v * v) * 255.);
}
// sqrt(V) - V table
softLightLUTSquareRoot = new int[256];
for (int i = 0; i < 256; i++) {
double v = i / 255.;
softLightLUTSquareRoot[i] = (int) ((Math.sqrt(v) - v) * 255.);
}
}
public void clear() {
clear(0);
}
public void clear(int color) {
for (int i = 0; i < width * height; i++)
data[i] = color;
}
public void clear(CPRect r, int color) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left; i < rect.right; i++)
data[i + j * width] = color;
}
public int[] getData() {
return data;
}
public int getAlpha() {
return alpha;
}
public int getBlendMode() {
return blendMode;
}
public void copyFrom(CPLayer l) {
name = l.name;
blendMode = l.blendMode;
alpha = l.alpha;
visible = l.visible;
copyDataFrom(l);
}
// Layer blending functions
//
// The FullAlpha versions are the ones that work in all cases
// others need the bottom layer to be 100% opaque but are faster
public void fusionWith(CPLayer fusion, CPRect r) {
if (alpha <= 0)
return;
switch (blendMode) {
case LM_NORMAL:
if (alpha >= 100)
fusionWithNormalNoAlpha(fusion, r);
else
fusionWithNormal(fusion, r);
break;
case LM_MULTIPLY:
fusionWithMultiply(fusion, r);
break;
case LM_ADD:
fusionWithAdd(fusion, r);
break;
case LM_SCREEN:
fusionWithScreenFullAlpha(fusion, r);
break;
case LM_LIGHTEN:
fusionWithLightenFullAlpha(fusion, r);
break;
case LM_DARKEN:
fusionWithDarkenFullAlpha(fusion, r);
break;
case LM_SUBTRACT:
fusionWithSubtractFullAlpha(fusion, r);
break;
case LM_DODGE:
fusionWithDodgeFullAlpha(fusion, r);
break;
case LM_BURN:
fusionWithBurnFullAlpha(fusion, r);
break;
case LM_OVERLAY:
fusionWithOverlayFullAlpha(fusion, r);
break;
case LM_HARDLIGHT:
fusionWithHardLightFullAlpha(fusion, r);
break;
case LM_SOFTLIGHT:
fusionWithSoftLightFullAlpha(fusion, r);
break;
case LM_VIVIDLIGHT:
fusionWithVividLightFullAlpha(fusion, r);
break;
case LM_LINEARLIGHT:
fusionWithLinearLightFullAlpha(fusion, r);
break;
case LM_PINLIGHT:
fusionWithPinLightFullAlpha(fusion, r);
break;
}
}
public void fusionWithFullAlpha(CPLayer fusion, CPRect r) {
if (alpha <= 0)
return;
switch (blendMode) {
case LM_NORMAL:
fusionWithNormalFullAlpha(fusion, r);
break;
case LM_MULTIPLY:
fusionWithMultiplyFullAlpha(fusion, r);
break;
case LM_ADD:
fusionWithAddFullAlpha(fusion, r);
break;
case LM_SCREEN:
fusionWithScreenFullAlpha(fusion, r);
break;
case LM_LIGHTEN:
fusionWithLightenFullAlpha(fusion, r);
break;
case LM_DARKEN:
fusionWithDarkenFullAlpha(fusion, r);
break;
case LM_SUBTRACT:
fusionWithSubtractFullAlpha(fusion, r);
break;
case LM_DODGE:
fusionWithDodgeFullAlpha(fusion, r);
break;
case LM_BURN:
fusionWithBurnFullAlpha(fusion, r);
break;
case LM_OVERLAY:
fusionWithOverlayFullAlpha(fusion, r);
break;
case LM_HARDLIGHT:
fusionWithHardLightFullAlpha(fusion, r);
break;
case LM_SOFTLIGHT:
fusionWithSoftLightFullAlpha(fusion, r);
break;
case LM_VIVIDLIGHT:
fusionWithVividLightFullAlpha(fusion, r);
break;
case LM_LINEARLIGHT:
fusionWithLinearLightFullAlpha(fusion, r);
break;
case LM_PINLIGHT:
fusionWithPinLightFullAlpha(fusion, r);
break;
}
}
public void fusionWithMultiply(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha = (color1 >>> 24) * this.alpha / 100;
if (alpha == 0)
continue;
else {
int color2 = fusion.data[off];
color1 = color1 ^ 0xffffffff;
fusion.data[off] = 0xff000000
| (color2 >>> 16 & 0xff) - (color1 >>> 16 & 0xff)
* (color2 >>> 16 & 0xff) * alpha / (255 * 255) << 16
| (color2 >>> 8 & 0xff) - (color1 >>> 8 & 0xff)
* (color2 >>> 8 & 0xff) * alpha / (255 * 255) << 8
| (color2 & 0xff) - (color1 & 0xff) * (color2 & 0xff) * alpha
/ (255 * 255);
}
}
}
}
public void fusionWithNormal(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha = (color1 >>> 24) * this.alpha / 100;
if (alpha == 0)
continue;
else if (alpha == 255)
fusion.data[off] = color1;
else {
int color2 = fusion.data[off];
int invAlpha = 255 - alpha;
fusion.data[off] = 0xff000000
| ((color1 >>> 16 & 0xff) * alpha + (color2 >>> 16 & 0xff)
* invAlpha) / 255 << 16
| ((color1 >>> 8 & 0xff) * alpha + (color2 >>> 8 & 0xff)
* invAlpha) / 255 << 8
| ((color1 & 0xff) * alpha + (color2 & 0xff) * invAlpha) / 255;
}
}
}
}
public void fusionWithNormalNoAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha = color1 >>> 24;
if (alpha == 0)
continue;
else if (alpha == 255)
fusion.data[off] = color1;
else {
int color2 = fusion.data[off];
int invAlpha = 255 - alpha;
fusion.data[off] = 0xff000000
| ((color1 >>> 16 & 0xff) * alpha + (color2 >>> 16 & 0xff)
* invAlpha) / 255 << 16
| ((color1 >>> 8 & 0xff) * alpha + (color2 >>> 8 & 0xff)
* invAlpha) / 255 << 8
| ((color1 & 0xff) * alpha + (color2 & 0xff) * invAlpha) / 255;
}
}
}
}
public void fusionWithAdd(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha = (color1 >>> 24) * this.alpha / 100;
if (alpha == 0)
continue;
else {
int color2 = fusion.data[off];
int r = Math.min(255, (color2 >>> 16 & 0xff) + alpha
* (color1 >>> 16 & 0xff) / 255);
int g = Math.min(255, (color2 >>> 8 & 0xff) + alpha
* (color1 >>> 8 & 0xff) / 255);
int b = Math
.min(255, (color2 & 0xff) + alpha * (color1 & 0xff) / 255);
fusion.data[off] = 0xff000000 | r << 16 | g << 8 | b;
}
}
}
}
// Normal Alpha Mode
// C = A*d + B*(1-d) and d = aa / (aa + ab - aa*ab)
public void fusionWithNormalFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int realAlpha = alpha1 * 255 / newAlpha;
int invAlpha = 255 - realAlpha;
fusion.data[off] = newAlpha << 24
| ((color1 >>> 16 & 0xff) * realAlpha + (color2 >>> 16 & 0xff)
* invAlpha) / 255 << 16
| ((color1 >>> 8 & 0xff) * realAlpha + (color2 >>> 8 & 0xff)
* invAlpha) / 255 << 8
| ((color1 & 0xff) * realAlpha + (color2 & 0xff) * invAlpha)
/ 255;
}
}
}
fusion.alpha = 100;
}
// Multiply Mode
// C = (A*aa*(1-ab) + B*ab*(1-aa) + A*B*aa*ab) / (aa + ab - aa*ab)
public void fusionWithMultiplyFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
fusion.data[off] = newAlpha << 24
| ((color1 >>> 16 & 0xff) * alpha1n2 + (color2 >>> 16 & 0xff)
* alphan12 + (color1 >>> 16 & 0xff) * (color2 >>> 16 & 0xff)
* alpha12 / 255)
/ newAlpha << 16
| ((color1 >>> 8 & 0xff) * alpha1n2 + (color2 >>> 8 & 0xff)
* alphan12 + (color1 >>> 8 & 0xff) * (color2 >>> 8 & 0xff)
* alpha12 / 255)
/ newAlpha << 8
| ((color1 & 0xff) * alpha1n2 + (color2 & 0xff) * alphan12 + (color1 & 0xff)
* (color2 & 0xff) * alpha12 / 255) / newAlpha;
}
}
}
fusion.alpha = 100;
}
// Linear Dodge (Add) Mode
// C = (aa * A + ab * B) / (aa + ab - aa*ab)
public void fusionWithAddFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
/*
* // this version seems slower than the Math.min one int r = (alpha2
* * (color2 >>> 16 & 0xff) + alpha1 * (color1 >>> 16 & 0xff)) /
* newAlpha; r |= ((~((r & 0xffffff00) - 1) >> 16) | r) & 0xff; int g
* = (alpha2 * (color2 >>> 8 & 0xff) + alpha1 * (color1 >>> 8 & 0xff))
* / newAlpha; g |= ((~((g & 0xffffff00) - 1) >> 16) | g) & 0xff; int
* b = (alpha2 * (color2 & 0xff) + alpha1 * (color1 & 0xff)) /
* newAlpha; b |= ((~((b & 0xffffff00) - 1) >> 16) | b) & 0xff;
*/
int r = Math.min(255, (alpha2 * (color2 >>> 16 & 0xff) + alpha1
* (color1 >>> 16 & 0xff))
/ newAlpha);
int g = Math.min(255, (alpha2 * (color2 >>> 8 & 0xff) + alpha1
* (color1 >>> 8 & 0xff))
/ newAlpha);
int b = Math.min(255, (alpha2 * (color2 & 0xff) + alpha1
* (color1 & 0xff))
/ newAlpha);
fusion.data[off] = newAlpha << 24 | r << 16 | g << 8 | b;
}
}
}
fusion.alpha = 100;
}
// Linear Burn (Sub) Mode
// C = (aa * A + ab * B - aa*ab ) / (aa + ab - aa*ab)
public void fusionWithSubtractFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2;
int r = (alpha2 * (color2 >>> 16 & 0xff) + alpha1
* (color1 >>> 16 & 0xff) - alpha12)
/ newAlpha;
r = r & ~r >>> 24; // binary magic to clamp negative values to zero
// without using a condition
int g = (alpha2 * (color2 >>> 8 & 0xff) + alpha1
* (color1 >>> 8 & 0xff) - alpha12)
/ newAlpha;
g = g & ~g >>> 24;
int b = (alpha2 * (color2 & 0xff) + alpha1 * (color1 & 0xff) - alpha12)
/ newAlpha;
b = b & ~b >>> 24;
fusion.data[off] = newAlpha << 24 | r << 16 | g << 8 | b;
}
}
}
fusion.alpha = 100;
}
// Screen Mode
// same as Multiply except all color channels are inverted and the result too
// C = 1 - (((1-A)*aa*(1-ab) + (1-B)*ab*(1-aa) + (1-A)*(1-B)*aa*ab) / (aa + ab
// - aa*ab))
public void fusionWithScreenFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
color1 ^= 0xffffff;
color2 ^= 0xffffff;
fusion.data[off] = newAlpha << 24
| 0xffffff
^ (((color1 >>> 16 & 0xff) * alpha1n2 + (color2 >>> 16 & 0xff)
* alphan12 + (color1 >>> 16 & 0xff) * (color2 >>> 16 & 0xff)
* alpha12 / 255)
/ newAlpha << 16
| ((color1 >>> 8 & 0xff) * alpha1n2 + (color2 >>> 8 & 0xff)
* alphan12 + (color1 >>> 8 & 0xff)
* (color2 >>> 8 & 0xff) * alpha12 / 255)
/ newAlpha << 8 | ((color1 & 0xff) * alpha1n2
+ (color2 & 0xff) * alphan12 + (color1 & 0xff)
* (color2 & 0xff) * alpha12 / 255)
/ newAlpha);
}
}
}
fusion.alpha = 100;
}
// Lighten Mode
// if B >= A: C = A*d + B*(1-d) and d = aa * (1-ab) / (aa + ab - aa*ab)
// if A > B: C = B*d + A*(1-d) and d = ab * (1-aa) / (aa + ab - aa*ab)
public void fusionWithLightenFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
// This alpha is used when color1 > color2
int alpha12 = alpha2 * (alpha1 ^ 0xff) / newAlpha;
int invAlpha12 = alpha12 ^ 0xff;
// This alpha is used when color2 > color1
int alpha21 = alpha1 * (alpha2 ^ 0xff) / newAlpha;
int invAlpha21 = alpha21 ^ 0xff;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (c2 >= c1 ? c1 * alpha21 + c2 * invAlpha21 : c2 * alpha12
+ c1 * invAlpha12) / 255 << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (c2 >= c1 ? c1 * alpha21 + c2 * invAlpha21 : c2 * alpha12
+ c1 * invAlpha12) / 255 << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (c2 >= c1 ? c1 * alpha21 + c2 * invAlpha21 : c2 * alpha12
+ c1 * invAlpha12) / 255;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Darken Mode
// if B >= A: C = B*d + A*(1-d) and d = ab * (1-aa) / (aa + ab - aa*ab)
// if A > B: C = A*d + B*(1-d) and d = aa * (1-ab) / (aa + ab - aa*ab)
public void fusionWithDarkenFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
// This alpha is used when color1 > color2
int alpha12 = alpha1 * (alpha2 ^ 0xff) / newAlpha;
int invAlpha12 = alpha12 ^ 0xff;
// This alpha is used when color2 > color1
int alpha21 = alpha2 * (alpha1 ^ 0xff) / newAlpha;
int invAlpha21 = alpha21 ^ 0xff;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (c2 >= c1 ? c2 * alpha21 + c1 * invAlpha21 : c1 * alpha12
+ c2 * invAlpha12) / 255 << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (c2 >= c1 ? c2 * alpha21 + c1 * invAlpha21 : c1 * alpha12
+ c2 * invAlpha12) / 255 << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (c2 >= c1 ? c2 * alpha21 + c1 * invAlpha21 : c1 * alpha12
+ c2 * invAlpha12) / 255;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Dodge Mode
//
// C = (aa*(1-ab)*A + (1-aa)*ab*B + aa*ab*B/(1-A)) / (aa + ab - aa*ab)
public void fusionWithDodgeFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int invColor1 = color1 ^ 0xffffffff;
fusion.data[off] = newAlpha << 24
| ((color1 >>> 16 & 0xff) * alpha1n2 + (color2 >>> 16 & 0xff)
* alphan12 + alpha12
* ((invColor1 >>> 16 & 0xff) == 0 ? 255 : Math.min(255, 255
* (color2 >>> 16 & 0xff) / (invColor1 >>> 16 & 0xff))))
/ newAlpha << 16
| ((color1 >>> 8 & 0xff) * alpha1n2 + (color2 >>> 8 & 0xff)
* alphan12 + alpha12
* ((invColor1 >>> 8 & 0xff) == 0 ? 255 : Math.min(255, 255
* (color2 >>> 8 & 0xff) / (invColor1 >>> 8 & 0xff))))
/ newAlpha << 8
| ((color1 & 0xff) * alpha1n2 + (color2 & 0xff) * alphan12 + alpha12
* ((invColor1 & 0xff) == 0 ? 255 : Math.min(255, 255
* (color2 & 0xff) / (invColor1 & 0xff)))) / newAlpha;
}
}
}
fusion.alpha = 100;
}
// Burn Mode
//
// C = (aa*(1-ab)*A + (1-aa)*ab*B + aa*ab*(1-(1-B)/A)) / (aa + ab - aa*ab)
public void fusionWithBurnFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int invColor2 = color2 ^ 0xffffffff;
fusion.data[off] = newAlpha << 24
| ((color1 >>> 16 & 0xff) * alpha1n2 + (color2 >>> 16 & 0xff)
* alphan12 + alpha12
* ((color1 >>> 16 & 0xff) == 0 ? 0
: Math.min(255, 255 * (invColor2 >>> 16 & 0xff)
/ (color1 >>> 16 & 0xff)) ^ 0xff))
/ newAlpha << 16
| ((color1 >>> 8 & 0xff) * alpha1n2 + (color2 >>> 8 & 0xff)
* alphan12 + alpha12
* ((color1 >>> 8 & 0xff) == 0 ? 0
: Math.min(255, 255 * (invColor2 >>> 8 & 0xff)
/ (color1 >>> 8 & 0xff)) ^ 0xff))
/ newAlpha << 8
| ((color1 & 0xff) * alpha1n2 + (color2 & 0xff) * alphan12 + alpha12
* ((color1 & 0xff) == 0 ? 0 : Math.min(255, 255
* (invColor2 & 0xff) / (color1 & 0xff)) ^ 0xff))
/ newAlpha;
}
}
}
fusion.alpha = 100;
}
// Overlay Mode
// If B <= 0.5 C = (A*aa*(1-ab) + B*ab*(1-aa) + aa*ab*(2*A*B) / (aa + ab -
// aa*ab)
// If B > 0.5 C = (A*aa*(1-ab) + B*ab*(1-aa) + aa*ab*(1 - 2*(1-A)*(1-B)) / (aa
// + ab - aa*ab)
public void fusionWithOverlayFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c2 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c2 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c2 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Hard Light Mode (same as Overlay with A and B swapped)
// If A <= 0.5 C = (A*aa*(1-ab) + B*ab*(1-aa) + aa*ab*(2*A*B) / (aa + ab -
// aa*ab)
// If A > 0.5 C = (A*aa*(1-ab) + B*ab*(1-aa) + aa*ab*(1 - 2*(1-A)*(1-B)) / (aa
// + ab - aa*ab)
public void fusionWithHardLightFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12 * 2
* c1 * c2 / 255 : alpha12
* (2 * (c1 ^ 0xff) * (c2 ^ 0xff) / 255 ^ 0xff)))
/ newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Soft Light Mode
// A < 0.5 => C = (2*A - 1) * (B - B^2) + B
// A > 0.5 => C = (2*A - 1) * (sqrt(B) - B) + B
public void fusionWithSoftLightFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* ((2 * c1 - 255) * softLightLUTSquare[c2] / 255 + c2) : alpha12
* ((2 * c1 - 255) * softLightLUTSquareRoot[c2] / 255 + c2)))
/ newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* ((2 * c1 - 255) * softLightLUTSquare[c2] / 255 + c2) : alpha12
* ((2 * c1 - 255) * softLightLUTSquareRoot[c2] / 255 + c2)))
/ newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* ((2 * c1 - 255) * softLightLUTSquare[c2] / 255 + c2) : alpha12
* ((2 * c1 - 255) * softLightLUTSquareRoot[c2] / 255 + c2)))
/ newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Vivid Light Mode
// A < 0.5 => C = 1 - (1-B) / (2*A)
// A > 0.5 => C = B / (2*(1-A))
public void fusionWithVividLightFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* (c1 == 0 ? 0 : 255 - Math.min(255, (255 - c2) * 255 / (2 * c1)))
: alpha12
* (c1 == 255 ? 255 : Math.min(255, c2 * 255
/ (2 * (255 - c1))))))
/ newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* (c1 == 0 ? 0 : 255 - Math.min(255, (255 - c2) * 255 / (2 * c1)))
: alpha12
* (c1 == 255 ? 255 : Math.min(255, c2 * 255
/ (2 * (255 - c1))))))
/ newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + (c1 <= 127 ? alpha12
* (c1 == 0 ? 0 : 255 - Math.min(255, (255 - c2) * 255 / (2 * c1)))
: alpha12
* (c1 == 255 ? 255 : Math.min(255, c2 * 255
/ (2 * (255 - c1))))))
/ newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Linear Light Mode
// C = B + 2*A -1
public void fusionWithLinearLightFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12
* Math.min(255, Math.max(0, c2 + 2 * c1 - 255)))
/ newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12
* Math.min(255, Math.max(0, c2 + 2 * c1 - 255)))
/ newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12
* Math.min(255, Math.max(0, c2 + 2 * c1 - 255)))
/ newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
// Pin Light Mode
// B > 2*A => C = 2*A
// B < 2*A-1 => C = 2*A-1
// else => C = B
public void fusionWithPinLightFullAlpha(CPLayer fusion, CPRect rc) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(rc);
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
for (int i = rect.left; i < rect.right; i++, off++) {
int color1 = data[off];
int alpha1 = (color1 >>> 24) * alpha / 100;
if (alpha1 == 0)
continue;
int color2 = fusion.data[off];
int alpha2 = (color2 >>> 24) * fusion.alpha / 100;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int alpha12 = alpha1 * alpha2 / 255;
int alpha1n2 = alpha1 * (alpha2 ^ 0xff) / 255;
int alphan12 = (alpha1 ^ 0xff) * alpha2 / 255;
int color = newAlpha << 24;
int c1, c2, c3;
c1 = color1 >>> 16 & 0xff;
c2 = color2 >>> 16 & 0xff;
c3 = c2 >= 2 * c1 ? 2 * c1 : c2 <= 2 * c1 - 255 ? 2 * c1 - 255 : c2;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12 * c3) / newAlpha << 16;
c1 = color1 >>> 8 & 0xff;
c2 = color2 >>> 8 & 0xff;
c3 = c2 >= 2 * c1 ? 2 * c1 : c2 <= 2 * c1 - 255 ? 2 * c1 - 255 : c2;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12 * c3) / newAlpha << 8;
c1 = color1 & 0xff;
c2 = color2 & 0xff;
c3 = c2 >= 2 * c1 ? 2 * c1 : c2 <= 2 * c1 - 255 ? 2 * c1 - 255 : c2;
color |= (alpha1n2 * c1 + alphan12 * c2 + alpha12 * c3) / newAlpha;
fusion.data[off] = color;
}
}
}
fusion.alpha = 100;
}
public void setAlpha(int alpha) {
this.alpha = alpha;
}
public void setBlendMode(int blendMode) {
this.blendMode = blendMode;
}
public void rendererCheckboard(CPRect r) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left; i < rect.right; i++)
if ((i & 0x8) != 0 ^ (j & 0x8) != 0)
data[i + j * width] = 0xffffffff;
else
data[i + j * width] = 0xffcccccc;
}
public void copyRegionHFlip(CPRect r, CPLayer source) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left, s = rect.right - 1; i < rect.right; i++, s--)
data[i + j * width] = source.data[s + j * width];
}
public void copyRegionVFlip(CPRect r, CPLayer source) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
for (int j = rect.top, s = rect.bottom - 1; j < rect.bottom; j++, s--)
for (int i = rect.left; i < rect.right; i++)
data[i + j * width] = source.data[i + s * width];
}
public void fillWithNoise(CPRect r) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
int value;
Random rnd = new Random();
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left; i < rect.right; i++) {
value = rnd.nextInt();
value &= 0xff;
value |= value << 8 | value << 16 | 0xff000000;
data[i + j * width] = value;
}
}
public void fillWithColorNoise(CPRect r) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
Random rnd = new Random();
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left; i < rect.right; i++)
data[i + j * width] = rnd.nextInt() | 0xff000000;
}
public void invert(CPRect r) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
for (int j = rect.top; j < rect.bottom; j++)
for (int i = rect.left; i < rect.right; i++)
data[i + j * width] ^= 0xffffff;
}
public boolean hasAlpha() {
if (alpha != 100)
return true;
int andPixels = 0xff000000;
int max = width * height;
for (int i = 0; i < max; i++)
andPixels &= data[i];
return andPixels != 0xff000000;
}
public boolean hasAlpha(CPRect r) {
if (alpha != 100)
return true;
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
int andPixels = 0xff000000;
for (int j = rect.top; j < rect.bottom; j++) {
int off = rect.left + j * width;
int max = off + rect.right - rect.left;
for (; off < max; off++)
andPixels &= data[off];
}
return andPixels != 0xff000000;
}
}