/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.palette;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jwildfire.base.Tools;
import org.jwildfire.create.tina.animate.AnimAware;
import org.jwildfire.create.tina.base.motion.MotionCurve;
import org.jwildfire.create.tina.edit.Assignable;
import org.jwildfire.image.Pixel;
import org.jwildfire.image.SimpleImage;
import org.jwildfire.transform.BalancingTransformer;
import org.jwildfire.transform.HSLTransformer;
import org.jwildfire.transform.SwapRGBTransformer;
import org.jwildfire.transform.SwapRGBTransformer.Mode;
public class RGBPalette implements Assignable<RGBPalette>, Serializable {
private static final long serialVersionUID = 1L;
public static final int PALETTE_SIZE = 256;
static final RGBColor BLACK = new RGBColor(0, 0, 0);
private int highestIdx = -1;
private boolean modified = true;
@AnimAware
private int modRed;
@AnimAware
private int modGreen;
@AnimAware
private int modBlue;
@AnimAware
private int modShift;
private final MotionCurve modShiftCurve = new MotionCurve();
@AnimAware
private int modHue;
@AnimAware
private int modContrast;
@AnimAware
private int modGamma;
@AnimAware
private int modBrightness;
@AnimAware
private int modSaturation;
@AnimAware
private int modSwapRGB;
@AnimAware
private int modFrequency = 1;
@AnimAware
private int modBlur;
private String flam3Number; // imported from flam3, just for display
private String flam3Name; // imported from flam3, just for display
private Map<Integer, RGBColor> rawColors = new HashMap<Integer, RGBColor>();
private final RGBColor[] transformedColors = new RGBColor[PALETTE_SIZE];
private GradientSelectionProvider selectionProvider = new DefaultGradientSelectionProvider();
public void addColor(int pRed, int pGreen, int pBlue) {
RGBColor color = new RGBColor(pRed, pGreen, pBlue);
if (highestIdx + 1 == PALETTE_SIZE) {
throw new IllegalStateException("Palette is already complete");
}
else {
rawColors.put(Integer.valueOf(++highestIdx), color);
}
modified = true;
}
public void setColor(int pIndex, int pRed, int pGreen, int pBlue) {
if (pIndex < 0 || pIndex >= PALETTE_SIZE)
throw new IllegalArgumentException(pIndex + " is no valid index");
RGBColor color = new RGBColor(pRed, pGreen, pBlue);
rawColors.put(Integer.valueOf(pIndex), color);
modified = true;
}
public RGBColor getColor(int pIdx) {
transformColors();
RGBColor res = transformedColors[pIdx];
return res != null ? res : BLACK;
}
public RGBColor getRawColor(int pIdx) {
RGBColor res = rawColors.get(pIdx);
return res != null ? res : BLACK;
}
public RenderColor[] createRenderPalette(double pWhiteLevel) {
transformColors();
RenderColor res[] = new RenderColor[PALETTE_SIZE];
for (int i = 0; i < res.length; i++) {
res[i] = new RenderColor();
RGBColor color = transformedColors[i];
int r, g, b;
if (color != null) {
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
}
else {
RGBColor leftColor = null, rightColor = null;
int leftIdx = i, rightIdx = i;
while (leftIdx-- >= 0) {
leftColor = transformedColors[leftIdx];
if (leftColor != null) {
break;
}
}
while (rightIdx++ < PALETTE_SIZE) {
rightColor = transformedColors[rightIdx];
if (rightColor != null) {
break;
}
}
if (leftColor != null && rightColor != null) {
r = leftColor.getRed() + (rightColor.getRed() - leftColor.getRed()) * (leftIdx - i) / (leftIdx - rightIdx);
g = leftColor.getGreen() + (rightColor.getGreen() - leftColor.getGreen()) * (leftIdx - i) / (leftIdx - rightIdx);
b = leftColor.getBlue() + (rightColor.getBlue() - leftColor.getBlue()) * (leftIdx - i) / (leftIdx - rightIdx);
}
else if (leftColor != null && rightColor == null) {
r = leftColor.getRed();
g = leftColor.getGreen();
b = leftColor.getBlue();
}
else if (leftColor == null && rightColor != null) {
r = rightColor.getRed();
g = rightColor.getGreen();
b = rightColor.getBlue();
}
else {
r = BLACK.getRed();
g = BLACK.getGreen();
b = BLACK.getBlue();
}
}
double DFLT_WHITE_LEVEL = 200.0;
res[i].red = (r * DFLT_WHITE_LEVEL) / 256.0;
res[i].green = (g * DFLT_WHITE_LEVEL) / 256.0;
res[i].blue = (b * DFLT_WHITE_LEVEL) / 256.0;
}
return res;
}
public int getSize() {
return rawColors.size();
}
public int getModRed() {
return modRed;
}
public void setModRed(int modRed) {
this.modRed = modRed;
modified = true;
}
public int getModGreen() {
return modGreen;
}
public void setModGreen(int modGreen) {
this.modGreen = modGreen;
modified = true;
}
public int getModBlue() {
return modBlue;
}
public void setModBlue(int modBlue) {
this.modBlue = modBlue;
modified = true;
}
public int getModShift() {
return modShift;
}
public void setModShift(int modShift) {
this.modShift = modShift;
modified = true;
}
public int getModHue() {
return modHue;
}
public void setModHue(int modHue) {
this.modHue = modHue;
modified = true;
}
public int getModContrast() {
return modContrast;
}
public void setModContrast(int modContrast) {
this.modContrast = modContrast;
modified = true;
}
public int getModGamma() {
return modGamma;
}
public void setModGamma(int modGamma) {
this.modGamma = modGamma;
modified = true;
}
public int getModBrightness() {
return modBrightness;
}
public void setModBrightness(int modBrightness) {
this.modBrightness = modBrightness;
modified = true;
}
public int getModSwapRGB() {
return modSwapRGB;
}
public void setModSwapRGB(int modSwapRGB) {
this.modSwapRGB = modSwapRGB;
modified = true;
}
public int getModSaturation() {
return modSaturation;
}
public void setModSaturation(int modSaturation) {
this.modSaturation = modSaturation;
modified = true;
}
private void transformColors() {
int startIdx = selectionProvider.getFrom();
int endIdx = selectionProvider.getTo();
if (modified) {
for (int i = 0; i < transformedColors.length; i++) {
transformedColors[i] = null;
}
for (int i = 0; i < PALETTE_SIZE; i++) {
RGBColor color = getRawColor(i);
int idx = i + modShift;
if (idx < 0) {
idx += PALETTE_SIZE;
}
else if (idx >= PALETTE_SIZE) {
idx -= PALETTE_SIZE;
}
transformedColors[idx] = new RGBColor(color.getRed(), color.getGreen(), color.getBlue());
}
if (modFrequency > 1) {
RGBColor[] newTransformedColors = new RGBColor[PALETTE_SIZE];
System.arraycopy(transformedColors, 0, newTransformedColors, 0, PALETTE_SIZE);
int n = PALETTE_SIZE / modFrequency;
for (int j = 0; j < modFrequency; j++) {
for (int i = 0; i < n; i++) {
int idx = i + j * n;
if (idx < PALETTE_SIZE) {
newTransformedColors[idx] = transformedColors[i * modFrequency];
}
}
}
System.arraycopy(newTransformedColors, 0, transformedColors, 0, PALETTE_SIZE);
newTransformedColors = null;
}
if (modBlur > 0) {
RGBColor[] newTransformedColors = new RGBColor[PALETTE_SIZE];
System.arraycopy(transformedColors, 0, newTransformedColors, 0, PALETTE_SIZE);
for (int i = 0; i < PALETTE_SIZE; i++) {
int r = 0;
int g = 0;
int b = 0;
int n = -1;
for (int j = i - modBlur; j <= i + modBlur; j++) {
n++;
int k = (PALETTE_SIZE + j) % PALETTE_SIZE;
if (k != i) {
RGBColor color = transformedColors[k];
r += color.getRed();
g += color.getGreen();
b += color.getBlue();
}
}
if (n != 0) {
RGBColor color = new RGBColor(Tools.limitColor(r / n), Tools.limitColor(g / n), Tools.limitColor(b / n));
newTransformedColors[i] = color;
}
}
System.arraycopy(newTransformedColors, 0, transformedColors, 0, PALETTE_SIZE);
newTransformedColors = null;
}
SimpleImage img = new RGBPaletteRenderer().renderHorizPalette(transformedColors, PALETTE_SIZE, 1);
if (modRed != 0 || modGreen != 0 || modBlue != 0 || modContrast != 0 || modGamma != 0 || modBrightness != 0 || modSaturation != 0) {
BalancingTransformer bT = new BalancingTransformer();
bT.setRed(modRed);
bT.setGreen(modGreen);
bT.setBlue(modBlue);
bT.setContrast(modContrast);
bT.setGamma(modGamma);
bT.setBrightness(modBrightness);
bT.setSaturation(modSaturation);
bT.transformImage(img);
}
if (modHue != 0) {
HSLTransformer hT = new HSLTransformer();
hT.setHue(modHue);
hT.transformImage(img);
}
if (modSwapRGB != 0) {
SwapRGBTransformer sT = new SwapRGBTransformer();
int maxValues = Mode.values().length;
int idx = (int) ((double) Math.abs(modSwapRGB) / (double) 255.0 * (double) (maxValues - 1));
sT.setMode(Mode.values()[idx]);
sT.transformImage(img);
}
Pixel pixel = new Pixel();
for (int i = startIdx; i <= endIdx; i++) {
RGBColor color = transformedColors[i];
pixel.setARGBValue(img.getARGBValue(i, 0));
color.setRed(pixel.r);
color.setGreen(pixel.g);
color.setBlue(pixel.b);
}
modified = false;
}
}
public RGBColor[] getTransformedColors() {
transformColors();
return transformedColors;
}
@Override
public RGBPalette makeCopy() {
RGBPalette res = new RGBPalette();
res.assign(this);
return res;
}
public RGBPalette makeDeepCopy() {
RGBPalette res = new RGBPalette();
res.assign(this);
res.assignRawColors(this);
return res;
}
public void assignRawColors(RGBPalette pRGBPalette) {
rawColors.clear();
for (Integer key : pRGBPalette.rawColors.keySet()) {
RGBColor newColor = pRGBPalette.rawColors.get(key).makeCopy();
rawColors.put(key, newColor);
}
}
@Override
public void assign(RGBPalette pRGBPalette) {
highestIdx = pRGBPalette.highestIdx;
modified = pRGBPalette.modified;
modRed = pRGBPalette.modRed;
modGreen = pRGBPalette.modGreen;
modBlue = pRGBPalette.modBlue;
modShift = pRGBPalette.modShift;
modShiftCurve.assign(pRGBPalette.modShiftCurve);
modHue = pRGBPalette.modHue;
modContrast = pRGBPalette.modContrast;
modGamma = pRGBPalette.modGamma;
modBrightness = pRGBPalette.modBrightness;
modSaturation = pRGBPalette.modSaturation;
modSwapRGB = pRGBPalette.modSwapRGB;
modFrequency = pRGBPalette.modFrequency;
modBlur = pRGBPalette.modBlur;
flam3Name = pRGBPalette.flam3Name;
flam3Number = pRGBPalette.flam3Number;
/*
rawColors.clear();
for (Integer key : pRGBPalette.rawColors.keySet()) {
RGBColor newColor = pRGBPalette.rawColors.get(key).makeCopy();
rawColors.put(key, newColor);
}
*/
// raw colors may not be modified
rawColors.clear();
rawColors.putAll(pRGBPalette.rawColors);
System.arraycopy(pRGBPalette.transformedColors, 0, transformedColors, 0, PALETTE_SIZE);
}
public String getFlam3Number() {
return flam3Number;
}
public void setFlam3Number(String flam3Number) {
this.flam3Number = flam3Number;
}
public String getFlam3Name() {
return flam3Name;
}
public void setFlam3Name(String flam3Name) {
this.flam3Name = flam3Name;
}
@Override
public String toString() {
if (flam3Number != null && flam3Number.length() > 0) {
if (flam3Name != null && flam3Name.length() > 0) {
return flam3Number + " - " + flam3Name;
}
else {
return flam3Name;
}
}
else if (flam3Name != null && flam3Name.length() > 0) {
return flam3Name;
}
else {
return "untitled gradient";
}
}
@Override
public boolean isEqual(RGBPalette pSrc) {
// flam3Number and flam3Name can not be changed
if ((modRed != pSrc.modRed) || (modGreen != pSrc.modGreen) || (modBlue != pSrc.modBlue) ||
(modShift != pSrc.modShift) || !modShiftCurve.isEqual(pSrc.modShiftCurve) ||
(modHue != pSrc.modHue) || (modContrast != pSrc.modContrast) ||
(modGamma != pSrc.modGamma) || (modBrightness != pSrc.modBrightness) || (modSaturation != pSrc.modSaturation) ||
(modSwapRGB != pSrc.modSwapRGB) || (modFrequency != pSrc.modFrequency) || (modBlur != pSrc.modBlur) ||
(rawColors.size() != pSrc.rawColors.size())) {
return false;
}
for (int i = 0; i < PALETTE_SIZE; i++) {
RGBColor color = rawColors.get(i);
RGBColor srcColor = pSrc.rawColors.get(i);
if (!color.isEqual(srcColor)) {
return false;
}
}
return true;
}
private void doSort(int pStartIdx, int pEndIdx) {
List<RGBColor> colors = new ArrayList<RGBColor>();
for (int i = pStartIdx; i <= pEndIdx; i++) {
colors.add(rawColors.get(i));
}
Collections.sort(colors, new Comparator<RGBColor>() {
@Override
public int compare(RGBColor o1, RGBColor o2) {
return o1.compareToRGBColor(o2);
}
});
for (int i = pStartIdx; i <= pEndIdx; i++) {
rawColors.put(Integer.valueOf(i), colors.get(i - pStartIdx));
}
modified = true;
}
public void sort() {
doSort(selectionProvider.getFrom(), selectionProvider.getTo());
}
private void doNegativeColors(int pStartIdx, int pEndIdx) {
for (int i = pStartIdx; i <= pEndIdx; i++) {
RGBColor color = rawColors.get(Integer.valueOf(i));
if (color != null) {
color.setRed(255 - color.getRed());
color.setGreen(255 - color.getGreen());
color.setBlue(255 - color.getBlue());
}
}
modified = true;
}
public void negativeColors() {
doNegativeColors(selectionProvider.getFrom(), selectionProvider.getTo());
}
public void reverseColors() {
doReverseColors(selectionProvider.getFrom(), selectionProvider.getTo());
}
private void doReverseColors(int pStartIdx, int pEndIdx) {
Map<Integer, RGBColor> newRawColors = new HashMap<Integer, RGBColor>();
newRawColors.putAll(rawColors);
for (int i = pStartIdx; i <= pEndIdx; i++) {
RGBColor color = rawColors.get(i);
// System.out.println(pStartIdx + " " + pEndIdx + ": " + i + "->" + Integer.valueOf(pEndIdx - (i - pStartIdx)));
if (color != null) {
newRawColors.put(Integer.valueOf(pEndIdx - (i - pStartIdx)), color);
}
}
rawColors = newRawColors;
modified = true;
}
public int getModFrequency() {
return modFrequency;
}
public void setModFrequency(int modFrequency) {
this.modFrequency = modFrequency;
modified = true;
}
public int getModBlur() {
return modBlur;
}
public void setModBlur(int modBlur) {
this.modBlur = modBlur;
modified = true;
}
public void fadeRange(int pStartIdx, int pEndIdx) {
if (pStartIdx < pEndIdx + 1) {
RGBColor lColor = getRawColor(pStartIdx);
RGBColor rColor = getRawColor(pEndIdx);
double scl = 1.0 / (pEndIdx - pStartIdx);
for (int i = pStartIdx + 1; i < pEndIdx; i++) {
double x = scl * (i - pStartIdx);
int r = Tools.roundColor((double) lColor.getRed() + ((double) (rColor.getRed() - lColor.getRed())) * x);
int g = Tools.roundColor((double) lColor.getGreen() + ((double) (rColor.getGreen() - lColor.getGreen())) * x);
int b = Tools.roundColor((double) lColor.getBlue() + ((double) (rColor.getBlue() - lColor.getBlue())) * x);
rawColors.put(Integer.valueOf(i), new RGBColor(r, g, b));
}
modified = true;
}
}
public void setSelectionProvider(GradientSelectionProvider pSelectionProvider) {
selectionProvider = pSelectionProvider;
modified = true;
}
public void monochrome(int pStartIdx, int pEndIdx) {
if (pStartIdx < pEndIdx + 1) {
Pixel rgbPixel = new Pixel();
HSLTransformer.HSLPixel hslPixel = new HSLTransformer.HSLPixel();
double avgHue = 0.0;
int cnt = 0;
for (int i = pStartIdx; i <= pEndIdx; i++) {
RGBColor color = getRawColor(i);
rgbPixel.setRGB(color.getRed(), color.getGreen(), color.getBlue());
HSLTransformer.rgb2hsl(rgbPixel, hslPixel);
avgHue += hslPixel.hue;
cnt++;
}
avgHue /= (double) cnt;
for (int i = pStartIdx; i <= pEndIdx; i++) {
RGBColor color = getRawColor(i);
rgbPixel.setRGB(color.getRed(), color.getGreen(), color.getBlue());
HSLTransformer.rgb2hsl(rgbPixel, hslPixel);
hslPixel.hue = avgHue;
HSLTransformer.hsl2rgb(hslPixel, rgbPixel);
color.setRed(rgbPixel.r);
color.setGreen(rgbPixel.g);
color.setBlue(rgbPixel.b);
rawColors.put(i, color);
}
modified = true;
}
}
public void setColors(Map<Integer, RGBColor> pColors, boolean pDoInterpolate, boolean pDoFade) {
if (pColors.size() > 0) {
int maxIdx = -1;
for (Integer idx : pColors.keySet()) {
if (idx > maxIdx) {
maxIdx = idx;
}
}
int srcSize = maxIdx + 1;
if (pDoInterpolate) {
setColorsSmooth(pColors, srcSize, pDoFade);
}
else {
setColorsNonInterpolate(pColors, srcSize);
}
}
}
private void setColorsNonInterpolate(Map<Integer, RGBColor> pColors, int pSrcSize) {
int dstSize = PALETTE_SIZE;
for (int i = 0; i < dstSize; i++) {
double coord = (double) (i * (pSrcSize - 1)) / (double) (dstSize - 1);
int colorIdx = (int) (coord);
RGBColor lColor = pColors.get(colorIdx);
if (lColor == null) {
lColor = BLACK;
}
RGBColor rColor = pColors.get(colorIdx + 1);
if (rColor == null) {
rColor = BLACK;
}
double x = coord - colorIdx;
int r = Tools.roundColor((double) lColor.getRed() + ((double) (rColor.getRed() - lColor.getRed())) * x);
int g = Tools.roundColor((double) lColor.getGreen() + ((double) (rColor.getGreen() - lColor.getGreen())) * x);
int b = Tools.roundColor((double) lColor.getBlue() + ((double) (rColor.getBlue() - lColor.getBlue())) * x);
rawColors.put(Integer.valueOf(i), new RGBColor(r, g, b));
}
}
private void setColorsSmooth(Map<Integer, RGBColor> pColors, int pSrcSize, boolean pDoFade) {
int dstSize = PALETTE_SIZE;
for (int i = 0; i < dstSize; i++) {
double coord = (double) (i * (pSrcSize - 1)) / (double) (dstSize - 1);
RGBColor lColor = null;
int lColorIdx = 0;
for (int j = (int) coord; j >= 0; j--) {
lColor = pColors.get(j);
if (lColor != null) {
lColorIdx = j;
break;
}
}
if (lColor == null) {
lColor = BLACK;
lColorIdx = 0;
}
RGBColor rColor = null;
int rColorIdx = 0;
for (int j = ((int) coord) + 1; j < pSrcSize; j++) {
rColor = pColors.get(j);
if (rColor != null) {
rColorIdx = j;
break;
}
}
if (rColor == null) {
rColor = BLACK;
rColorIdx = pSrcSize - 1;
}
double srcRange = rColorIdx - lColorIdx + 1;
double x = pDoFade ? (coord - lColorIdx) / srcRange : 0.0;
int r = Tools.roundColor((double) lColor.getRed() + ((double) (rColor.getRed() - lColor.getRed())) * x);
int g = Tools.roundColor((double) lColor.getGreen() + ((double) (rColor.getGreen() - lColor.getGreen())) * x);
int b = Tools.roundColor((double) lColor.getBlue() + ((double) (rColor.getBlue() - lColor.getBlue())) * x);
rawColors.put(Integer.valueOf(i), new RGBColor(r, g, b));
}
}
public void applyBalancing() {
modified = true;
int saveModSwapRGB = modSwapRGB;
int saveModFrequency = modFrequency;
int saveModBlur = modBlur;
try {
modSwapRGB = 0;
modFrequency = 0;
modBlur = 0;
transformColors();
rawColors.clear();
for (int i = 0; i < transformedColors.length; i++) {
rawColors.put(Integer.valueOf(i), transformedColors[i]);
}
modRed = 0;
modGreen = 0;
modBlue = 0;
modShift = 0;
modHue = 0;
modContrast = 0;
modGamma = 0;
modBrightness = 0;
modSaturation = 0;
modified = true;
}
finally {
modSwapRGB = saveModSwapRGB;
modFrequency = saveModFrequency;
modBlur = saveModBlur;
}
}
public void applyTX() {
int saveModRed = modRed;
int saveModGreen = modGreen;
int saveModBlue = modBlue;
int saveModShift = modShift;
int saveModHue = modHue;
int saveModContrast = modContrast;
int saveModGamma = modGamma;
int saveModBrightness = modBrightness;
int saveModSaturation = modSaturation;
try {
modRed = 0;
modGreen = 0;
modBlue = 0;
modShift = 0;
modHue = 0;
modContrast = 0;
modGamma = 0;
modBrightness = 0;
modSaturation = 0;
transformColors();
rawColors.clear();
for (int i = 0; i < transformedColors.length; i++) {
rawColors.put(Integer.valueOf(i), transformedColors[i]);
}
modSwapRGB = 0;
modFrequency = 0;
modBlur = 0;
modified = true;
}
finally {
modRed = saveModRed;
modGreen = saveModGreen;
modBlue = saveModBlue;
modShift = saveModShift;
modHue = saveModHue;
modContrast = saveModContrast;
modGamma = saveModGamma;
modBrightness = saveModBrightness;
modSaturation = saveModSaturation;
}
}
public void shiftColors(int pShift) {
if (pShift != 0) {
while (pShift < -255) {
pShift += 256;
}
while (pShift > 255) {
pShift -= 256;
}
Map<Integer, RGBColor> newColors = new HashMap<Integer, RGBColor>();
for (int i = 0; i < PALETTE_SIZE; i++) {
RGBColor color = getRawColor(i);
int idx = i + pShift;
if (idx < 0) {
idx += PALETTE_SIZE;
}
else if (idx >= PALETTE_SIZE) {
idx -= PALETTE_SIZE;
}
newColors.put(idx, new RGBColor(color.getRed(), color.getGreen(), color.getBlue()));
}
rawColors = null;
rawColors = newColors;
modified = true;
}
}
}