/*
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.*;
//
// A 32bpp bitmap class (ARGB format)
//
public class CPColorBmp extends CPBitmap {
// The bitmap data
public int[] data;
//
// Constructors
//
// Allocates a new bitmap
public CPColorBmp(int width, int height) {
super(width, height);
data = new int[width * height];
}
// Creates a CPBitmap object from existing bitmap data
public CPColorBmp(int width, int height, int[] data) {
super(width, height);
this.data = data;
}
// Creates a CPBitmap by copying a part of another CPBitmap
public CPColorBmp(CPColorBmp bmp, CPRect r) {
super(r.getWidth(), r.getWidth());
width = r.getWidth();
height = r.getHeight();
data = new int[width * height];
setFromBitmapRect(bmp, r);
}
//
// Pixel access with friendly clipping
//
public int getPixel(int x, int y) {
x = Math.max(0, Math.min(width - 1, x));
y = Math.max(0, Math.min(height - 1, y));
return data[x + y * width];
}
public void setPixel(int x, int y, int color) {
if (x < 0 || y < 0 || x >= width || y >= height)
return;
data[x + y * width] = color;
}
//
// Copy rectangular regions methods
//
// copies a a rect to an int array allocated by the method
public int[] copyRectToIntArray(CPRect rect) {
CPRect r = new CPRect(0, 0, width, height);
r.clip(rect);
int[] buffer = new int[r.getWidth() * r.getHeight()];
int w = r.getWidth();
int h = r.getHeight();
for (int j = 0; j < h; j++)
System.arraycopy(data, (j + r.top) * width + r.left, buffer, j * w, w);
return buffer;
}
// copies a a rect to an int array
public boolean copyRectToIntArray(CPRect rect, int[] buffer) {
CPRect r = new CPRect(0, 0, width, height);
r.clip(rect);
if (buffer.length < r.getWidth() * r.getHeight())
return false;
int w = r.getWidth();
int h = r.getHeight();
for (int j = 0; j < h; j++)
System.arraycopy(data, (j + r.top) * width + r.left, buffer, j * w, w);
return true;
}
// sets the content of a rectangular region using data from an int array
public void setRectFromIntArray(int[] buffer, CPRect rect) {
CPRect r = new CPRect(0, 0, width, height);
r.clip(rect);
int w = r.getWidth();
int h = r.getHeight();
for (int j = 0; j < h; j++)
System.arraycopy(buffer, j * w, data, (j + r.top) * width + r.left, w);
}
//
// XOR Copy
//
public int[] copyRectXOR(CPColorBmp bmp, CPRect rect) {
CPRect r = new CPRect(0, 0, width, height);
r.clip(rect);
int[] buffer = new int[r.getWidth() * r.getHeight()];
int w = r.getWidth();
int h = r.getHeight();
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
buffer[i + j * w] = data[(j + r.top) * width + i + r.left]
^ bmp.data[(j + r.top) * width + i + r.left];
return buffer;
}
public void setRectXOR(int[] buffer, CPRect rect) {
CPRect r = new CPRect(0, 0, width, height);
r.clip(rect);
int w = r.getWidth();
int h = r.getHeight();
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
data[(j + r.top) * width + i + r.left] = data[(j + r.top) * width + i
+ r.left]
^ buffer[i + j * w];
}
//
// Copy another bitmap into this one using alpha blending
//
public void pasteAlphaRect(CPColorBmp bmp, CPRect srcRect, int x, int y) {
CPRect srcRectCpy = (CPRect) srcRect.clone(), dstRect = new CPRect(x, y, 0,
0);
getSize().clipSourceDest(srcRectCpy, dstRect);
int[] srcData = bmp.data;
for (int j = 0; j < dstRect.bottom - dstRect.top; j++) {
int srcOffset = srcRectCpy.left + (srcRectCpy.top + j) * bmp.width;
int dstOffset = dstRect.left + (dstRect.top + j) * width;
for (int i = dstRect.left; i < dstRect.right; i++, srcOffset++, dstOffset++) {
int color1 = srcData[srcOffset];
int alpha1 = color1 >>> 24;
if (alpha1 <= 0)
continue;
if (alpha1 == 255) {
data[dstOffset] = color1;
continue;
}
int color2 = data[dstOffset];
int alpha2 = color2 >>> 24;
int newAlpha = alpha1 + alpha2 - alpha1 * alpha2 / 255;
if (newAlpha > 0) {
int realAlpha = alpha1 * 255 / newAlpha;
int invAlpha = 255 - realAlpha;
data[dstOffset] = newAlpha << 24
| (color1 >>> 16 & 0xff)
+ ((color2 >>> 16 & 0xff) * invAlpha - (color1 >>> 16 & 0xff)
* invAlpha) / 255 << 16
| (color1 >>> 8 & 0xff)
+ ((color2 >>> 8 & 0xff) * invAlpha - (color1 >>> 8 & 0xff)
* invAlpha) / 255 << 8 | (color1 & 0xff)
+ ((color2 & 0xff) * invAlpha - (color1 & 0xff) * invAlpha) / 255;
}
}
}
}
// Sets the content of this CPBitmap using a rect from another bitmap
// Assumes that the width and height of this bitmap and the rectangle are the
// same!!!
public void setFromBitmapRect(CPColorBmp bmp, CPRect r) {
for (int i = 0; i < r.getHeight(); i++)
System.arraycopy(bmp.data, (i + r.top) * bmp.width + r.left, data, i
* width, width);
}
public void pasteBitmap(CPColorBmp bmp, int x, int y) {
CPRect srcRect = bmp.getSize();
CPRect dstRect = new CPRect(x, y, 0, 0);
getSize().clipSourceDest(srcRect, dstRect);
for (int i = 0; i < srcRect.getHeight(); i++)
System.arraycopy(bmp.data, (i + srcRect.top) * bmp.width + srcRect.left,
data, (i + dstRect.top) * width + dstRect.left, srcRect.getWidth());
}
//
// Copies the Alpha channel from another bitmap
//
public void copyAlphaFrom(CPColorBmp bmp, CPRect r) {
r.clip(getSize());
for (int j = r.top; j < r.bottom; j++)
for (int i = r.left; i < r.right; i++)
data[j * width + i] = data[j * width + i] & 0xffffff
| bmp.data[j * width + i] & 0xff000000;
}
public void copyDataFrom(CPColorBmp bmp) {
if (bmp.width != width || bmp.height != height) {
width = bmp.width;
height = bmp.height;
data = new int[width * height];
}
System.arraycopy(bmp.data, 0, data, 0, data.length);
}
//
// Flood fill algorithm
//
static class CPFillLine {
int x1, x2, y, dy;
CPFillLine(int x1, int x2, int y, int dy) {
this.x1 = x1;
this.x2 = x2;
this.y = y;
this.dy = dy;
}
}
public void floodFill(int x, int y, int color) {
if (!isInside(x, y))
return;
int oldColor, colorMask;
oldColor = getPixel(x, y);
// If we are filling 100% transparent areas
// then we need to ignore the residual color information
// (it would also be possible to clear it when erasing, but then
// the performance impact would be on the eraser rather than
// on this low importance flood fill)
if ((oldColor & 0xff000000) == 0) {
colorMask = 0xff000000;
oldColor = 0;
} else
colorMask = 0xffffffff;
if (color == oldColor)
return;
LinkedList<CPFillLine> stack = new LinkedList<CPFillLine>();
stack.addLast(new CPFillLine(x, x, y, -1));
stack.addLast(new CPFillLine(x, x, y + 1, 1));
CPRect clip = new CPRect(width, height);
while (!stack.isEmpty()) {
CPFillLine line = stack.removeFirst();
if (line.y < clip.top || line.y >= clip.bottom)
continue;
int lineOffset = line.y * width;
int left = line.x1, next;
while (left >= clip.left
&& (data[left + lineOffset] & colorMask) == oldColor) {
data[left + lineOffset] = color;
left--;
}
if (left >= line.x1) {
while (left <= line.x2
&& (data[left + lineOffset] & colorMask) != oldColor)
left++;
next = left + 1;
if (left > line.x2)
continue;
} else {
left++;
if (left < line.x1)
stack.addLast(new CPFillLine(left, line.x1 - 1, line.y - line.dy,
-line.dy));
next = line.x1 + 1;
}
do {
data[left + lineOffset] = color;
while (next < clip.right
&& (data[next + lineOffset] & colorMask) == oldColor) {
data[next + lineOffset] = color;
next++;
}
stack
.addLast(new CPFillLine(left, next - 1, line.y + line.dy, line.dy));
if (next - 1 > line.x2)
stack.addLast(new CPFillLine(line.x2 + 1, next - 1, line.y - line.dy,
-line.dy));
left = next + 1;
while (left <= line.x2
&& (data[left + lineOffset] & colorMask) != oldColor)
left++;
next = left + 1;
} while (left <= line.x2);
}
}
//
// Box Blur algorithm
//
public void boxBlur(CPRect r, int radiusX, int radiusY) {
CPRect rect = new CPRect(0, 0, width, height);
rect.clip(r);
int w = rect.getWidth();
int h = rect.getHeight();
int l = Math.max(w, h);
int[] src = new int[l];
int[] dst = new int[l];
for (int j = rect.top; j < rect.bottom; j++) {
System.arraycopy(data, rect.left + j * width, src, 0, w);
multiplyAlpha(src, w);
boxBlurLine(src, dst, w, radiusX);
System.arraycopy(dst, 0, data, rect.left + j * width, w);
}
for (int i = rect.left; i < rect.right; i++) {
copyColumnToArray(i, rect.top, h, src);
boxBlurLine(src, dst, h, radiusY);
separateAlpha(dst, h);
copyArrayToColumn(i, rect.top, h, dst);
}
}
public void multiplyAlpha(int[] buffer, int len) {
for (int i = 0; i < len; i++)
buffer[i] = buffer[i] & 0xff000000
| (buffer[i] >>> 24) * (buffer[i] >>> 16 & 0xff) / 255 << 16
| (buffer[i] >>> 24) * (buffer[i] >>> 8 & 0xff) / 255 << 8
| (buffer[i] >>> 24) * (buffer[i] & 0xff) / 255;
}
public void separateAlpha(int[] buffer, int len) {
for (int i = 0; i < len; i++)
if ((buffer[i] & 0xff000000) != 0)
buffer[i] = buffer[i]
& 0xff000000
| Math.min((buffer[i] >>> 16 & 0xff) * 255 / (buffer[i] >>> 24),
255) << 16
| Math
.min((buffer[i] >>> 8 & 0xff) * 255 / (buffer[i] >>> 24), 255) << 8
| Math.min((buffer[i] & 0xff) * 255 / (buffer[i] >>> 24), 255);
}
public void boxBlurLine(int[] src, int dst[], int len, int radius) {
int s, ta, tr, tg, tb;
s = ta = tr = tg = tb = 0;
int pix;
for (int i = 0; i < radius && i <= len; i++) {
pix = src[i];
ta += pix >>> 24;
tr += pix >>> 16 & 0xff;
tg += pix >>> 8 & 0xff;
tb += pix & 0xff;
s++;
}
for (int i = 0; i < len; i++) {
if (i + radius < len) {
pix = src[i + radius];
ta += pix >>> 24;
tr += pix >>> 16 & 0xff;
tg += pix >>> 8 & 0xff;
tb += pix & 0xff;
s++;
}
dst[i] = ta / s << 24 | tr / s << 16 | tg / s << 8 | tb / s;
if (i - radius >= 0) {
pix = src[i - radius];
ta -= pix >>> 24;
tr -= pix >>> 16 & 0xff;
tg -= pix >>> 8 & 0xff;
tb -= pix & 0xff;
s--;
}
}
}
public void copyColumnToArray(int x, int y, int len, int[] buffer) {
for (int i = 0; i < len; i++)
buffer[i] = data[x + (i + y) * width];
}
public void copyArrayToColumn(int x, int y, int len, int[] buffer) {
for (int i = 0; i < len; i++)
data[x + (i + y) * width] = buffer[i];
}
}