📄 cpartwork.java
字号:
/*
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 chibipaint.engine;
import java.util.*;
import chibipaint.*;
import chibipaint.engine.CPBrushManager.*;
import chibipaint.util.*;
//FIXME: BROKEN: use setForegroundColor and setBrush, controller's layerChanged replaced by the ICPArtworkListener mechanism
public class CPArtwork {
public int width, height;
Vector<CPLayer> layers;
CPLayer curLayer;
int activeLayer;
CPRect curSelection = new CPRect();
CPLayer fusion, undoBuffer, opacityBuffer;
CPRect fusionArea, undoArea, opacityArea;
Random rnd = new Random();
public interface ICPArtworkListener {
void updateRegion(CPArtwork artwork, CPRect region);
void layerChange(CPArtwork artwork);
}
private LinkedList<ICPArtworkListener> artworkListeners = new LinkedList();
// Clipboard
static private class CPClip {
CPColorBmp bmp;
int x, y;
CPClip(CPColorBmp bmp, int x, int y) {
this.bmp = bmp;
this.x = x;
this.y = y;
}
};
CPClip clipboard = null;
LinkedList<CPUndo> undoList, redoList;
private CPBrushInfo curBrush;
// FIXME: shouldn't be public
public CPBrushManager brushManager = new CPBrushManager();
float lastX, lastY, lastPressure;
int[] brushBuffer = null;
private int maxUndo = 30;
//
// Current Engine Parameters
//
boolean sampleAllLayers = false;
boolean lockAlpha = false;
int curColor;
CPBrushTool paintingModes[] = { new CPBrushToolSimpleBrush(), new CPBrushToolEraser(), new CPBrushToolDodge(),
new CPBrushToolBurn(), new CPBrushToolWatercolor(), new CPBrushToolBlur(), new CPBrushToolSmudge(),
new CPBrushToolOil(), };
static final int BURN_CONSTANT = 260;
static final int BLUR_MIN = 64;
static final int BLUR_MAX = 1;
public CPArtwork(int width, int height) {
this.width = width;
this.height = height;
layers = new Vector();
CPLayer defaultLayer = new CPLayer(width, height);
defaultLayer.name = getDefaultLayerName();
defaultLayer.clear(0xffffffff);
layers.add(defaultLayer);
curLayer = layers.get(0);
fusionArea = new CPRect(0, 0, width, height);
undoArea = new CPRect();
opacityArea = new CPRect();
activeLayer = 0;
curSelection.makeEmpty();
undoBuffer = new CPLayer(width, height);
// we reserve a double sized buffer to be used as a 16bits per channel buffer
opacityBuffer = new CPLayer(width, height);
fusion = new CPLayer(width, height);
undoList = new LinkedList();
redoList = new LinkedList();
}
public long getDocMemoryUsed() {
return (long) width * height * 4 * (3 + layers.size())
+ (clipboard != null ? clipboard.bmp.getWidth() * clipboard.bmp.getHeight() * 4 : 0);
}
public long getUndoMemoryUsed() {
long total = 0;
CPColorBmp lastBitmap = clipboard != null ? clipboard.bmp : null;
for (int i = redoList.size() - 1; i >= 0; i--) {
CPUndo undo = redoList.get(i);
total += undo.getMemoryUsed(true, lastBitmap);
}
for (CPUndo undo : undoList) {
total += undo.getMemoryUsed(false, lastBitmap);
}
return total;
}
public CPLayer getDisplayBM() {
fusionLayers();
return fusion;
// for(int i=0; i<opacityBuffer.data.length; i++)
// opacityBuffer.data[i] |= 0xff000000;
// return opacityBuffer;
}
public void fusionLayers() {
if (fusionArea.isEmpty()) {
return;
}
mergeOpacityBuffer(curColor, false);
fusion.clear(fusionArea, 0x00ffffff);
boolean fullAlpha = true, first = true;
for (CPLayer l : layers) {
if (!first) {
fullAlpha = fullAlpha && fusion.hasAlpha(fusionArea);
}
if (l.visible) {
first = false;
if (fullAlpha) {
l.fusionWithFullAlpha(fusion, fusionArea);
} else {
l.fusionWith(fusion, fusionArea);
}
}
}
fusionArea.makeEmpty();
}
// ///////////////////////////////////////////////////////////////////////////////////
// Listeners
// ///////////////////////////////////////////////////////////////////////////////////
public void addListener(ICPArtworkListener listener) {
artworkListeners.addLast(listener);
}
public void callListenersUpdateRegion(CPRect region) {
for (ICPArtworkListener l : artworkListeners) {
l.updateRegion(this, region);
}
}
public void callListenersLayerChange() {
for (ICPArtworkListener l : artworkListeners) {
l.layerChange(this);
}
}
// ///////////////////////////////////////////////////////////////////////////////////
// Global Parameters
// ///////////////////////////////////////////////////////////////////////////////////
public void setSampleAllLayers(boolean b) {
sampleAllLayers = b;
}
public void setLockAlpha(boolean b) {
lockAlpha = b;
}
public void setForegroundColor(int color) {
curColor = color;
}
public void setBrush(CPBrushInfo brush) {
curBrush = brush;
}
// ///////////////////////////////////////////////////////////////////////////////////
// Paint engine
// ///////////////////////////////////////////////////////////////////////////////////
public void beginStroke(float x, float y, float pressure) {
if (curBrush == null) {
return;
}
paintingModes[curBrush.paintMode].beginStroke(x, y, pressure);
}
public void continueStroke(float x, float y, float pressure) {
if (curBrush == null) {
return;
}
paintingModes[curBrush.paintMode].continueStroke(x, y, pressure);
}
public void endStroke() {
if (curBrush == null) {
return;
}
paintingModes[curBrush.paintMode].endStroke();
}
void mergeOpacityBuffer(int color, boolean clear) {
if (!opacityArea.isEmpty()) {
if (curBrush.paintMode != CPBrushInfo.M_ERASE || !lockAlpha) {
paintingModes[curBrush.paintMode].mergeOpacityBuf(opacityArea, color);
} else {
// FIXME: it would be nice to be able to set the paper color
paintingModes[CPBrushInfo.M_PAINT].mergeOpacityBuf(opacityArea, 0xffffff);
}
if (lockAlpha) {
restoreAlpha(opacityArea);
}
if (clear) {
opacityBuffer.clear(opacityArea, 0);
}
opacityArea.makeEmpty();
}
}
void restoreAlpha(CPRect r) {
getActiveLayer().copyAlphaFrom(undoBuffer, r);
}
// Extend this class to create new tools and brush types
abstract class CPBrushTool {
abstract public void beginStroke(float x, float y, float pressure);
abstract public void continueStroke(float x, float y, float pressure);
abstract public void endStroke();
abstract public void mergeOpacityBuf(CPRect dstRect, int color);
}
abstract class CPBrushToolBase extends CPBrushTool {
public void beginStroke(float x, float y, float pressure) {
undoBuffer.copyFrom(curLayer);
undoArea.makeEmpty();
opacityBuffer.clear();
opacityArea.makeEmpty();
lastX = x;
lastY = y;
lastPressure = pressure;
paintDab(x, y, pressure);
}
public void continueStroke(float x, float y, float pressure) {
float dist = (float) Math.sqrt(((lastX - x) * (lastX - x) + (lastY - y) * (lastY - y)));
float spacing = Math.max(curBrush.minSpacing, curBrush.curSize * curBrush.spacing);
if (dist > spacing) {
float nx = lastX, ny = lastY, np = lastPressure;
float df = (spacing - 0.001f) / dist;
for (float f = df; f <= 1.f; f += df) {
nx = f * x + (1.f - f) * lastX;
ny = f * y + (1.f - f) * lastY;
np = f * pressure + (1.f - f) * lastPressure;
paintDab(nx, ny, np);
}
lastX = nx;
lastY = ny;
lastPressure = np;
}
}
public void endStroke() {
undoArea.clip(getSize());
if (!undoArea.isEmpty()) {
mergeOpacityBuffer(curColor, false);
addUndo(new CPUndoPaint());
}
brushBuffer = null;
}
void paintDab(float x, float y, float pressure) {
curBrush.applyPressure(pressure);
if (curBrush.scattering > 0f) {
x += rnd.nextGaussian() * curBrush.curScattering / 4f;
y += rnd.nextGaussian() * curBrush.curScattering / 4f;
// x += (rnd.nextFloat() - .5f) * tool.scattering;
// y += (rnd.nextFloat() - .5f) * tool.scattering;
}
CPBrushDab dab = brushManager.getDab(x, y, curBrush);
paintDab(dab);
}
void paintDab(CPBrushDab dab) {
CPRect srcRect = new CPRect(dab.width, dab.height);
CPRect dstRect = new CPRect(dab.width, dab.height);
dstRect.translate(dab.x, dab.y);
clipSourceDest(srcRect, dstRect);
// drawing entirely outside the canvas
if (dstRect.isEmpty()) {
return;
}
undoArea.union(dstRect);
opacityArea.union(dstRect);
invalidateFusion(dstRect);
paintDabImplementation(srcRect, dstRect, dab);
}
abstract void paintDabImplementation(CPRect srcRect, CPRect dstRect, CPBrushDab dab);
}
class CPBrushToolSimpleBrush extends CPBrushToolBase {
void paintDabImplementation(CPRect srcRect, CPRect dstRect, CPBrushDab dab) {
// FIXME: there should be no reference to a specific tool here
// create a new brush parameter instead
if (curBrush.isAirbrush) {
paintFlow(srcRect, dstRect, dab.brush, dab.width, Math.max(1, dab.alpha / 8));
} else if (curBrush.toolNb == CPController.T_PEN) {
paintFlow(srcRect, dstRect, dab.brush, dab.width, Math.max(1, dab.alpha / 2));
} else {
// paintOpacityFlow(srcRect, dstRect, brush, dab.stride, alpha, 255);
// paintOpacityFlow(srcRect, dstRect, brush, dab.stride, 128, alpha);
paintOpacity(srcRect, dstRect, dab.brush, dab.width, dab.alpha);
}
}
public void mergeOpacityBuf(CPRect dstRect, int color) {
int[] opacityData = opacityBuffer.data;
int[] undoData = undoBuffer.data;
for (int j = dstRect.top; j < dstRect.bottom; j++) {
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, dstOffset++) {
int opacityAlpha = opacityData[dstOffset] / 255;
if (opacityAlpha > 0) {
int destColor = undoData[dstOffset];
int destAlpha = destColor >>> 24;
int newLayerAlpha = opacityAlpha + destAlpha * (255 - opacityAlpha) / 255;
int realAlpha = 255 * opacityAlpha / newLayerAlpha;
int invAlpha = 255 - realAlpha;
int newColor = (((color >>> 16 & 0xff) * realAlpha + (destColor >>> 16 & 0xff) * invAlpha) / 255) << 16
& 0xff0000
| (((color >>> 8 & 0xff) * realAlpha + (destColor >>> 8 & 0xff) * invAlpha) / 255) << 8
& 0xff00 | (((color & 0xff) * realAlpha + (destColor & 0xff) * invAlpha) / 255) & 0xff;
newColor |= newLayerAlpha << 24 & 0xff000000;
curLayer.data[dstOffset] = newColor;
}
}
}
}
void paintOpacity(CPRect srcRect, CPRect dstRect, byte[] brush, int w, int alpha) {
int[] opacityData = opacityBuffer.data;
int by = srcRect.top;
for (int j = dstRect.top; j < dstRect.bottom; j++, by++) {
int srcOffset = srcRect.left + by * w;
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, srcOffset++, dstOffset++) {
int brushAlpha = (brush[srcOffset] & 0xff) * alpha;
if (brushAlpha != 0) {
int opacityAlpha = opacityData[dstOffset];
if (brushAlpha > opacityAlpha) {
opacityData[dstOffset] = brushAlpha;
}
}
}
}
}
void paintFlow(CPRect srcRect, CPRect dstRect, byte[] brush, int w, int alpha) {
int[] opacityData = opacityBuffer.data;
int by = srcRect.top;
for (int j = dstRect.top; j < dstRect.bottom; j++, by++) {
int srcOffset = srcRect.left + by * w;
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, srcOffset++, dstOffset++) {
int brushAlpha = (brush[srcOffset] & 0xff) * alpha;
if (brushAlpha != 0) {
int opacityAlpha = Math.min(255 * 255, opacityData[dstOffset]
+ (255 - opacityData[dstOffset] / 255) * brushAlpha / 255);
opacityData[dstOffset] = opacityAlpha;
}
}
}
}
void paintOpacityFlow(CPRect srcRect, CPRect dstRect, byte[] brush, int w, int opacity, int flow) {
int[] opacityData = opacityBuffer.data;
int by = srcRect.top;
for (int j = dstRect.top; j < dstRect.bottom; j++, by++) {
int srcOffset = srcRect.left + by * w;
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, srcOffset++, dstOffset++) {
int brushAlpha = (brush[srcOffset] & 0xff) * flow;
if (brushAlpha != 0) {
int opacityAlpha = opacityData[dstOffset];
int newAlpha = Math.min(255 * 255, opacityAlpha + (opacity - opacityAlpha / 255) * brushAlpha
/ 255);
newAlpha = Math.min(opacity * (brush[srcOffset] & 0xff), newAlpha);
if (newAlpha > opacityAlpha) {
opacityData[dstOffset] = newAlpha;
}
}
}
}
}
}
class CPBrushToolEraser extends CPBrushToolSimpleBrush {
public void mergeOpacityBuf(CPRect dstRect, int color) {
int[] opacityData = opacityBuffer.data;
int[] undoData = undoBuffer.data;
for (int j = dstRect.top; j < dstRect.bottom; j++) {
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, dstOffset++) {
int opacityAlpha = opacityData[dstOffset] / 255;
if (opacityAlpha > 0) {
int destColor = undoData[dstOffset];
int destAlpha = destColor >>> 24;
int realAlpha = destAlpha * (255 - opacityAlpha) / 255;
curLayer.data[dstOffset] = destColor & 0xffffff | realAlpha << 24;
}
}
}
}
}
class CPBrushToolDodge extends CPBrushToolSimpleBrush {
public void mergeOpacityBuf(CPRect dstRect, int color) {
int[] opacityData = opacityBuffer.data;
int[] undoData = undoBuffer.data;
for (int j = dstRect.top; j < dstRect.bottom; j++) {
int dstOffset = dstRect.left + j * width;
for (int i = dstRect.left; i < dstRect.right; i++, dstOffset++) {
int opacityAlpha = opacityData[dstOffset] / 255;
if (opacityAlpha > 0) {
int destColor = undoData[dstOffset];
if ((destColor & 0xff000000) != 0) {
opacityAlpha += 255;
int r = (destColor >>> 16 & 0xff) * opacityAlpha / 255;
int g = (destColor >>> 8 & 0xff) * opacityAlpha / 255;
int b = (destColor & 0xff) * opacityAlpha / 255;
if (r > 255) {
r = 255;
}
if (g > 255) {
g = 255;
}
if (b > 255) {
b = 255;
}
int newColor = destColor & 0xff000000 | r << 16 | g << 8 | b;
curLayer.data[dstOffset] = newColor;
}
}
}
}
}
}
class CPBrushToolBurn extends CPBrushToolSimpleBrush {
public void mergeOpacityBuf(CPRect dstRect, int color) {
int[] opacityData = opacityBuffer.data;
int[] undoData = undoBuffer.data;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -