/* 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; public class CPBrushManager { byte[] brush, brushAA; byte[] cacheBrush; float cacheSize, cacheSqueeze, cacheAngle; int cacheType; CPGreyBmp texture; private static final float MAX_SQUEEZE = 10; public static class CPBrushDab { // the brush public byte[] brush; public int width, height; // and where and how to apply it public int x, y, alpha; } public CPBrushManager() { brush = new byte[201 * 201]; brushAA = new byte[202 * 202]; // test texture /* * texture = new byte[9]; texture[0] = 0; texture[1] = (byte) 255; * texture[2] = (byte) 255; texture[3] = 0; textureWidth = 2; textureHeight * = 2; */ } public CPBrushDab getDab(float x, float y, CPBrushInfo brushInfo) { CPBrushDab dab = new CPBrushDab(); dab.alpha = brushInfo.curAlpha; // FIXME: I don't like this special case for ROUND_PIXEL // it would be better to have brush presets for working with pixels boolean useAA = brushInfo.isAA && brushInfo.type != CPBrushInfo.B_ROUND_PIXEL; dab.width = (int) (brushInfo.curSize + .99f); dab.height = (int) (brushInfo.curSize + .99f); if (useAA) { dab.width++; dab.height++; } float nx = x - dab.width / 2.f + .5f; float ny = y - dab.height / 2.f + .5f; // this is necessary as Java uses convert towards zero float to int // conversion if (nx < 0) nx -= 1; if (ny < 0) ny -= 1; if (useAA) { float dx = Math.abs(nx - (int) nx); float dy = Math.abs(ny - (int) ny); dab.brush = getBrushWithAA(brushInfo, dx, dy); } else dab.brush = getBrush(brushInfo); dab.x = (int) nx; dab.y = (int) ny; if (brushInfo.texture > 0.f && texture != null) { // we need a brush bitmap that can be modified everytime // the one in "brush" can be kept in cache so if we are using it, make a // copy if (dab.brush == brush) { System.arraycopy(brush, 0, brushAA, 0, dab.width * dab.height); dab.brush = brushAA; } applyTexture(dab, brushInfo.texture); } return dab; } byte[] getBrush(CPBrushInfo brushInfo) { if (cacheBrush != null && brushInfo.curSize == cacheSize && brushInfo.curSqueeze == cacheSqueeze && brushInfo.curAngle == cacheAngle && brushInfo.type == cacheType) return cacheBrush; if (brushInfo.type == CPBrushInfo.B_ROUND_AIRBRUSH) brush = buildBrushSoft(brush, brushInfo); else if (brushInfo.type == CPBrushInfo.B_ROUND_AA) brush = buildBrushAA(brush, brushInfo); else if (brushInfo.type == CPBrushInfo.B_ROUND_PIXEL) brush = buildBrush(brush, brushInfo); else if (brushInfo.type == CPBrushInfo.B_SQUARE_AA) brush = buildBrushSquareAA(brush, brushInfo); else if (brushInfo.type == CPBrushInfo.B_SQUARE_PIXEL) brush = buildBrushSquare(brush, brushInfo); cacheBrush = brush; cacheSize = brushInfo.curSize; cacheType = brushInfo.type; cacheSqueeze = brushInfo.curSqueeze; cacheAngle = brushInfo.curAngle; return brush; } byte[] getBrushWithAA(CPBrushInfo brushInfo, float dx, float dy) { byte[] nonAABrush = getBrush(brushInfo); int intSize = (int) (brushInfo.curSize + .99f); int intSizeAA = (int) (brushInfo.curSize + .99f) + 1; for (int y = 0; y < intSizeAA; y++) for (int x = 0; x < intSizeAA; x++) brushAA[y * intSizeAA + x] = 0; for (int y = 0; y < intSize; y++) for (int x = 0; x < intSize; x++) { int brushAlpha = nonAABrush[y * intSize + x] & 0xff; brushAA[y * intSizeAA + x] += (int) (brushAlpha * (1 - dx) * (1 - dy)); brushAA[y * intSizeAA + x + 1] += (int) (brushAlpha * dx * (1 - dy)); brushAA[(y + 1) * intSizeAA + x + 1] += (int) (brushAlpha * dx * dy); brushAA[(y + 1) * intSizeAA + x] += (int) (brushAlpha * (1 - dx) * dy); } return brushAA; } byte[] buildBrush(byte[] brush, CPBrushInfo brushInfo) { int intSize = (int) (brushInfo.curSize + .99f); float center = intSize / 2.f; float sqrRadius = brushInfo.curSize / 2 * (brushInfo.curSize / 2); float xFactor = 1f + brushInfo.curSqueeze * MAX_SQUEEZE; float cosA = (float) Math.cos(brushInfo.curAngle); float sinA = (float) Math.sin(brushInfo.curAngle); int offset = 0; for (int j = 0; j < intSize; j++) for (int i = 0; i < intSize; i++) { float x = i + .5f - center; float y = j + .5f - center; float dx = (x * cosA - y * sinA) * xFactor; float dy = y * cosA + x * sinA; float sqrDist = dx * dx + dy * dy; if (sqrDist <= sqrRadius) brush[offset++] = (byte) 0xff; else brush[offset++] = 0; } return brush; } byte[] buildBrushAA(byte[] brush, CPBrushInfo brushInfo) { int intSize = (int) (brushInfo.curSize + .99f); float center = intSize / 2.f; float sqrRadius = brushInfo.curSize / 2 * (brushInfo.curSize / 2); float sqrRadiusInner = (brushInfo.curSize - 2) / 2 * ((brushInfo.curSize - 2) / 2); float sqrRadiusOuter = (brushInfo.curSize + 2) / 2 * ((brushInfo.curSize + 2) / 2); float xFactor = 1f + brushInfo.curSqueeze * MAX_SQUEEZE; float cosA = (float) Math.cos(brushInfo.curAngle); float sinA = (float) Math.sin(brushInfo.curAngle); int offset = 0; for (int j = 0; j < intSize; j++) for (int i = 0; i < intSize; i++) { float x = i + .5f - center; float y = j + .5f - center; float dx = (x * cosA - y * sinA) * xFactor; float dy = y * cosA + x * sinA; float sqrDist = dx * dx + dy * dy; if (sqrDist <= sqrRadiusInner) brush[offset++] = (byte) 0xff; else if (sqrDist > sqrRadiusOuter) brush[offset++] = 0; else { int count = 0; for (int oj = 0; oj < 4; oj++) for (int oi = 0; oi < 4; oi++) { x = i + oi * (1.f / 4.f) - center; y = j + oj * (1.f / 4.f) - center; dx = (x * cosA - y * sinA) * xFactor; dy = y * cosA + x * sinA; sqrDist = dx * dx + dy * dy; if (sqrDist <= sqrRadius) count += 1; } brush[offset++] = (byte) Math.min(count * 16, 255); } } return brush; } byte[] buildBrushSquare(byte[] brush, CPBrushInfo brushInfo) { int intSize = (int) (brushInfo.curSize + .99f); float center = intSize / 2.f; float size = brushInfo.curSize * (float) Math.sin(Math.PI / 4); float sizeX = size / 2 / (1f + brushInfo.curSqueeze * MAX_SQUEEZE); float sizeY = size / 2; float cosA = (float) Math.cos(brushInfo.curAngle); float sinA = (float) Math.sin(brushInfo.curAngle); int offset = 0; for (int j = 0; j < intSize; j++) for (int i = 0; i < intSize; i++) { float x = i + .5f - center; float y = j + .5f - center; float dx = Math.abs(x * cosA - y * sinA); float dy = Math.abs(y * cosA + x * sinA); if (dx <= sizeX && dy <= sizeY) brush[offset++] = (byte) 0xff; else brush[offset++] = 0; } return brush; } byte[] buildBrushSquareAA(byte[] brush, CPBrushInfo brushInfo) { int intSize = (int) (brushInfo.curSize + .99f); float center = intSize / 2.f; float size = brushInfo.curSize * (float) Math.sin(Math.PI / 4); float sizeX = size / 2 / (1f + brushInfo.curSqueeze * MAX_SQUEEZE); float sizeY = size / 2; float sizeXInner = sizeX - 1; float sizeYInner = sizeY - 1; float sizeXOuter = sizeX + 1; float sizeYOuter = sizeY + 1; float cosA = (float) Math.cos(brushInfo.curAngle); float sinA = (float) Math.sin(brushInfo.curAngle); int offset = 0; for (int j = 0; j < intSize; j++) for (int i = 0; i < intSize; i++) { float x = i + .5f - center; float y = j + .5f - center; float dx = Math.abs(x * cosA - y * sinA); float dy = Math.abs(y * cosA + x * sinA); if (dx <= sizeXInner && dy <= sizeYInner) brush[offset++] = (byte) 0xff; else if (dx > sizeXOuter || dy > sizeYOuter) brush[offset++] = 0; else { int count = 0; for (int oj = 0; oj < 4; oj++) for (int oi = 0; oi < 4; oi++) { x = i + oi * (1.f / 4.f) - center; y = j + oj * (1.f / 4.f) - center; dx = Math.abs(x * cosA - y * sinA); dy = Math.abs(y * cosA + x * sinA); if (dx <= sizeX && dy <= sizeY) count += 1; } brush[offset++] = (byte) Math.min(count * 16, 255); } } return brush; } byte[] buildBrushSoft(byte[] brush, CPBrushInfo brushInfo) { int intSize = (int) (brushInfo.curSize + .99f); float center = intSize / 2.f; float sqrRadius = brushInfo.curSize / 2 * (brushInfo.curSize / 2); float xFactor = 1f + brushInfo.curSqueeze * MAX_SQUEEZE; float cosA = (float) Math.cos(brushInfo.curAngle); float sinA = (float) Math.sin(brushInfo.curAngle); // byte[] brush = new int[size * size]; int offset = 0; for (int j = 0; j < intSize; j++) for (int i = 0; i < intSize; i++) { float x = i + .5f - center; float y = j + .5f - center; float dx = (x * cosA - y * sinA) * xFactor; float dy = y * cosA + x * sinA; float sqrDist = dx * dx + dy * dy; if (sqrDist <= sqrRadius) brush[offset++] = (byte) (255 * (1 - sqrDist / sqrRadius)); else brush[offset++] = 0; } return brush; } void applyTexture(CPBrushDab dab, float textureAmount) { int amount = (int) (textureAmount * 255f); int offset = 0; for (int j = 0; j < dab.height; j++) for (int i = 0; i < dab.width; i++) { int brushValue = dab.brush[offset] & 0xff; int textureX = (i + dab.x) % texture.width; if (textureX < 0) textureX += texture.width; int textureY = (j + dab.y) % texture.height; if (textureY < 0) textureY += texture.height; int textureValue = texture.data[textureX + textureY * texture.width] & 0xff; dab.brush[offset] = (byte) (brushValue * (textureValue * amount / 255 ^ 0xff) / 255); offset++; } } public void setTexture(CPGreyBmp texture) { this.texture = texture; } }