2014-02-19 10:38:30 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Copyright 2006 Jerry Huxtable
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2014-08-20 00:25:57 +02:00
|
|
|
package org.jivesoftware.smackx.jingleold.mediaimpl.sshare.api;
|
2007-05-02 21:55:59 +02:00
|
|
|
|
|
|
|
import java.io.PrintStream;
|
2015-03-23 09:27:15 +01:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2007-05-02 21:55:59 +02:00
|
|
|
import java.util.Vector;
|
2014-02-19 10:38:30 +01:00
|
|
|
import java.util.logging.Logger;
|
2008-10-30 22:20:29 +01:00
|
|
|
|
2007-05-02 21:55:59 +02:00
|
|
|
/**
|
|
|
|
* An image Quantizer based on the Octree algorithm. This is a very basic implementation
|
2018-05-09 23:06:12 +02:00
|
|
|
* at present and could be much improved by picking the nodes to reduce more carefully
|
2007-05-02 21:55:59 +02:00
|
|
|
* (i.e. not completely at random) when I get the time.
|
|
|
|
*/
|
|
|
|
public class OctTreeQuantizer implements Quantizer {
|
|
|
|
|
2017-02-07 22:02:40 +01:00
|
|
|
private static final Logger LOGGER = Logger.getLogger(OctTreeQuantizer.class.getName());
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The greatest depth the tree is allowed to reach
|
|
|
|
*/
|
2018-03-29 12:35:11 +02:00
|
|
|
static final int MAX_LEVEL = 5;
|
2017-02-07 22:02:40 +01:00
|
|
|
|
|
|
|
/**
|
2017-12-13 23:10:11 +01:00
|
|
|
* An Octree node.
|
2017-02-07 22:02:40 +01:00
|
|
|
*/
|
2017-02-11 16:16:41 +01:00
|
|
|
static class OctTreeNode {
|
2017-02-07 22:02:40 +01:00
|
|
|
int children;
|
|
|
|
int level;
|
|
|
|
OctTreeNode parent;
|
|
|
|
OctTreeNode[] leaf = new OctTreeNode[8];
|
|
|
|
boolean isLeaf;
|
|
|
|
int count;
|
|
|
|
int totalRed;
|
|
|
|
int totalGreen;
|
|
|
|
int totalBlue;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A debugging method which prints the tree out.
|
|
|
|
*/
|
|
|
|
public void list(PrintStream s, int level) {
|
|
|
|
String indentStr = "";
|
|
|
|
for (int i = 0; i < level; i++)
|
|
|
|
indentStr += " ";
|
|
|
|
if (count == 0)
|
|
|
|
LOGGER.fine(indentStr + index + ": count=" + count);
|
|
|
|
else
|
2017-05-23 16:45:04 +02:00
|
|
|
LOGGER.fine(indentStr + index + ": count=" + count + " red=" + (totalRed / count) + " green=" + (totalGreen / count) + " blue=" + (totalBlue / count));
|
2017-02-07 22:02:40 +01:00
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
if (leaf[i] != null)
|
2017-05-23 16:45:04 +02:00
|
|
|
leaf[i].list(s, level + 2);
|
2017-02-07 22:02:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int nodes = 0;
|
|
|
|
private OctTreeNode root;
|
|
|
|
private int reduceColors;
|
|
|
|
private int maximumColors;
|
|
|
|
private int colors = 0;
|
2017-12-13 23:10:11 +01:00
|
|
|
private final List<Vector<OctTreeNode>> colorList;
|
2015-03-17 11:33:02 +01:00
|
|
|
|
2015-03-23 09:27:15 +01:00
|
|
|
public OctTreeQuantizer() {
|
2017-02-07 22:02:40 +01:00
|
|
|
setup(256);
|
2017-05-23 16:45:04 +02:00
|
|
|
colorList = new ArrayList<>(MAX_LEVEL + 1);
|
|
|
|
for (int i = 0; i < MAX_LEVEL + 1; i++)
|
2017-02-07 22:02:40 +01:00
|
|
|
colorList.add(i, new Vector<OctTreeNode>());
|
|
|
|
root = new OctTreeNode();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the quantizer. This should be called before adding any pixels.
|
|
|
|
* @param numColors the number of colors we're quantizing to.
|
|
|
|
*/
|
2017-02-11 16:16:41 +01:00
|
|
|
@Override
|
2017-02-07 22:02:40 +01:00
|
|
|
public void setup(int numColors) {
|
|
|
|
maximumColors = numColors;
|
|
|
|
reduceColors = Math.max(512, numColors * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add pixels to the quantizer.
|
|
|
|
* @param pixels the array of ARGB pixels
|
|
|
|
* @param offset the offset into the array
|
|
|
|
* @param count the count of pixels
|
|
|
|
*/
|
2017-02-11 16:16:41 +01:00
|
|
|
@Override
|
2017-02-07 22:02:40 +01:00
|
|
|
public void addPixels(int[] pixels, int offset, int count) {
|
|
|
|
for (int i = 0; i < count; i++) {
|
2017-05-23 16:45:04 +02:00
|
|
|
insertColor(pixels[i + offset]);
|
2017-02-07 22:02:40 +01:00
|
|
|
if (colors > reduceColors)
|
|
|
|
reduceTree(reduceColors);
|
|
|
|
}
|
|
|
|
}
|
2007-05-02 21:55:59 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the color table index for a color.
|
|
|
|
* @param rgb the color
|
|
|
|
* @return the index
|
|
|
|
*/
|
2017-02-11 16:16:41 +01:00
|
|
|
@Override
|
2017-02-07 22:02:40 +01:00
|
|
|
public int getIndexForColor(int rgb) {
|
|
|
|
int red = (rgb >> 16) & 0xff;
|
|
|
|
int green = (rgb >> 8) & 0xff;
|
|
|
|
int blue = rgb & 0xff;
|
|
|
|
|
|
|
|
OctTreeNode node = root;
|
|
|
|
|
|
|
|
for (int level = 0; level <= MAX_LEVEL; level++) {
|
|
|
|
OctTreeNode child;
|
|
|
|
int bit = 0x80 >> level;
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
if ((red & bit) != 0)
|
|
|
|
index += 4;
|
|
|
|
if ((green & bit) != 0)
|
|
|
|
index += 2;
|
|
|
|
if ((blue & bit) != 0)
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
child = node.leaf[index];
|
|
|
|
|
|
|
|
if (child == null)
|
|
|
|
return node.index;
|
|
|
|
else if (child.isLeaf)
|
|
|
|
return child.index;
|
|
|
|
else
|
|
|
|
node = child;
|
|
|
|
}
|
|
|
|
LOGGER.fine("getIndexForColor failed");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void insertColor(int rgb) {
|
|
|
|
int red = (rgb >> 16) & 0xff;
|
|
|
|
int green = (rgb >> 8) & 0xff;
|
|
|
|
int blue = rgb & 0xff;
|
|
|
|
|
|
|
|
OctTreeNode node = root;
|
|
|
|
|
|
|
|
// LOGGER.fine("insertColor="+Integer.toHexString(rgb));
|
|
|
|
for (int level = 0; level <= MAX_LEVEL; level++) {
|
|
|
|
OctTreeNode child;
|
|
|
|
int bit = 0x80 >> level;
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
if ((red & bit) != 0)
|
|
|
|
index += 4;
|
|
|
|
if ((green & bit) != 0)
|
|
|
|
index += 2;
|
|
|
|
if ((blue & bit) != 0)
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
child = node.leaf[index];
|
|
|
|
|
|
|
|
if (child == null) {
|
|
|
|
node.children++;
|
|
|
|
|
|
|
|
child = new OctTreeNode();
|
|
|
|
child.parent = node;
|
|
|
|
node.leaf[index] = child;
|
|
|
|
node.isLeaf = false;
|
|
|
|
nodes++;
|
|
|
|
colorList.get(level).addElement(child);
|
|
|
|
|
|
|
|
if (level == MAX_LEVEL) {
|
|
|
|
child.isLeaf = true;
|
|
|
|
child.count = 1;
|
|
|
|
child.totalRed = red;
|
|
|
|
child.totalGreen = green;
|
|
|
|
child.totalBlue = blue;
|
|
|
|
child.level = level;
|
|
|
|
colors++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = child;
|
|
|
|
} else if (child.isLeaf) {
|
|
|
|
child.count++;
|
|
|
|
child.totalRed += red;
|
|
|
|
child.totalGreen += green;
|
|
|
|
child.totalBlue += blue;
|
|
|
|
return;
|
|
|
|
} else
|
|
|
|
node = child;
|
|
|
|
}
|
|
|
|
LOGGER.fine("insertColor failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void reduceTree(int numColors) {
|
2017-05-23 16:45:04 +02:00
|
|
|
for (int level = MAX_LEVEL - 1; level >= 0; level--) {
|
2017-02-07 22:02:40 +01:00
|
|
|
Vector<OctTreeNode> v = colorList.get(level);
|
|
|
|
if (v != null && v.size() > 0) {
|
|
|
|
for (int j = 0; j < v.size(); j++) {
|
|
|
|
OctTreeNode node = v.elementAt(j);
|
|
|
|
if (node.children > 0) {
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
OctTreeNode child = node.leaf[i];
|
|
|
|
if (child != null) {
|
|
|
|
if (!child.isLeaf)
|
|
|
|
LOGGER.fine("not a leaf!");
|
|
|
|
node.count += child.count;
|
|
|
|
node.totalRed += child.totalRed;
|
|
|
|
node.totalGreen += child.totalGreen;
|
|
|
|
node.totalBlue += child.totalBlue;
|
|
|
|
node.leaf[i] = null;
|
|
|
|
node.children--;
|
|
|
|
colors--;
|
|
|
|
nodes--;
|
2017-05-23 16:45:04 +02:00
|
|
|
colorList.get(level + 1).removeElement(child);
|
2017-02-07 22:02:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
node.isLeaf = true;
|
|
|
|
colors++;
|
|
|
|
if (colors <= numColors)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGGER.fine("Unable to reduce the OctTree");
|
|
|
|
}
|
2007-05-02 21:55:59 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Build the color table.
|
|
|
|
* @return the color table
|
|
|
|
*/
|
2017-02-11 16:16:41 +01:00
|
|
|
@Override
|
2017-02-07 22:02:40 +01:00
|
|
|
public int[] buildColorTable() {
|
|
|
|
int[] table = new int[colors];
|
|
|
|
buildColorTable(root, table, 0);
|
|
|
|
return table;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A quick way to use the quantizer. Just create a table the right size and pass in the pixels.
|
2007-05-02 21:55:59 +02:00
|
|
|
* @param inPixels the input colors
|
|
|
|
* @param table the output color table
|
|
|
|
*/
|
2017-02-07 22:02:40 +01:00
|
|
|
public void buildColorTable(int[] inPixels, int[] table) {
|
|
|
|
int count = inPixels.length;
|
|
|
|
maximumColors = table.length;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
insertColor(inPixels[i]);
|
|
|
|
if (colors > reduceColors)
|
|
|
|
reduceTree(reduceColors);
|
|
|
|
}
|
|
|
|
if (colors > maximumColors)
|
|
|
|
reduceTree(maximumColors);
|
|
|
|
buildColorTable(root, table, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int buildColorTable(OctTreeNode node, int[] table, int index) {
|
|
|
|
if (colors > maximumColors)
|
|
|
|
reduceTree(maximumColors);
|
|
|
|
|
|
|
|
if (node.isLeaf) {
|
|
|
|
int count = node.count;
|
|
|
|
table[index] = 0xff000000 |
|
2017-05-23 16:45:04 +02:00
|
|
|
((node.totalRed / count) << 16) |
|
|
|
|
((node.totalGreen / count) << 8) |
|
|
|
|
node.totalBlue / count;
|
2017-02-07 22:02:40 +01:00
|
|
|
node.index = index++;
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
if (node.leaf[i] != null) {
|
|
|
|
node.index = index;
|
|
|
|
index = buildColorTable(node.leaf[i], table, index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return index;
|
|
|
|
}
|
2015-03-17 11:33:02 +01:00
|
|
|
|
2007-05-02 21:55:59 +02:00
|
|
|
}
|
|
|
|
|