mirror of
https://github.com/octoleo/plantuml.git
synced 2024-12-22 10:59:01 +00:00
refactor: remove unused code
This commit is contained in:
parent
e92dcd400b
commit
f8d87217a7
2
attic.md
2
attic.md
@ -7,3 +7,5 @@ It serves as a historical reference to ensure we remember and understand past de
|
||||
- [basic language](https://github.com/plantuml/plantuml/tree/v1.2023.12/src/net/sourceforge/plantuml/jasic)
|
||||
- [mjpeg export](https://github.com/plantuml/plantuml/tree/v1.2023.12/src/net/sourceforge/plantuml/mjpeg)
|
||||
- [animation](https://github.com/plantuml/plantuml/tree/v1.2023.12/src/net/sourceforge/plantuml/anim)
|
||||
- [ditherer quantization](https://github.com/plantuml/plantuml/tree/v1.2023.12/src/net/sourceforge/plantuml/quantization)
|
||||
- [syntax suggestion](https://github.com/plantuml/plantuml/blob/v1.2023.12/src/net/sourceforge/plantuml/syntax/SyntaxChecker.java)
|
||||
|
@ -240,16 +240,13 @@ public class ImageBuilder {
|
||||
|
||||
private ImageData writeImageInternal(OutputStream os) throws IOException {
|
||||
XDimension2D dim = getFinalDimension();
|
||||
double dx = 0;
|
||||
double dy = 0;
|
||||
|
||||
final Scale scale = titledDiagram == null ? null : titledDiagram.getScale();
|
||||
final double scaleFactor = (scale == null ? 1 : scale.getScale(dim.getWidth(), dim.getHeight())) * getDpi()
|
||||
/ 96.0;
|
||||
if (scaleFactor <= 0)
|
||||
throw new IllegalStateException("Bad scaleFactor");
|
||||
WasmLog.log("...image drawing...");
|
||||
UGraphic ug = createUGraphic(dim, dx, dy, scaleFactor,
|
||||
UGraphic ug = createUGraphic(dim, scaleFactor,
|
||||
titledDiagram == null ? new Pragma() : titledDiagram.getPragma());
|
||||
maybeDrawBorder(ug, dim);
|
||||
if (randomPixel)
|
||||
@ -317,12 +314,12 @@ public class ImageBuilder {
|
||||
return ug;
|
||||
}
|
||||
|
||||
private UGraphic createUGraphic(final XDimension2D dim, double dx, double dy, double scaleFactor, Pragma pragma) {
|
||||
private UGraphic createUGraphic(final XDimension2D dim, double scaleFactor, Pragma pragma) {
|
||||
final ColorMapper colorMapper = fileFormatOption.getColorMapper();
|
||||
switch (fileFormatOption.getFileFormat()) {
|
||||
case PNG:
|
||||
case RAW:
|
||||
return createUGraphicPNG(scaleFactor, dim, dx, dy, fileFormatOption.getWatermark(),
|
||||
return createUGraphicPNG(scaleFactor, dim, fileFormatOption.getWatermark(),
|
||||
fileFormatOption.getFileFormat());
|
||||
case SVG:
|
||||
return createUGraphicSVG(scaleFactor, dim, pragma);
|
||||
@ -375,9 +372,8 @@ public class ImageBuilder {
|
||||
|
||||
}
|
||||
|
||||
private UGraphic createUGraphicPNG(double scaleFactor, final XDimension2D dim, double dx, double dy,
|
||||
String watermark, FileFormat format) {
|
||||
// ::done
|
||||
private UGraphic createUGraphicPNG(double scaleFactor, final XDimension2D dim, String watermark,
|
||||
FileFormat format) {
|
||||
Color pngBackColor = new Color(0, 0, 0, 0);
|
||||
|
||||
if (this.backcolor instanceof HColorSimple)
|
||||
@ -392,7 +388,7 @@ public class ImageBuilder {
|
||||
final Graphics2D graphics2D = builder.getGraphics2D();
|
||||
|
||||
final UGraphicG2d ug = new UGraphicG2d(backcolor, fileFormatOption.getColorMapper(), stringBounder, graphics2D,
|
||||
scaleFactor, dx, dy, format);
|
||||
scaleFactor, format);
|
||||
|
||||
ug.setBufferedImage(builder.getBufferedImage());
|
||||
final BufferedImage im = ug.getBufferedImage();
|
||||
|
@ -83,7 +83,7 @@ public class OptionFlags {
|
||||
static public void setMaxPixel(int max) {
|
||||
}
|
||||
|
||||
static public final boolean USE_HECTOR = false;
|
||||
// static public final boolean USE_HECTOR = false;
|
||||
static public boolean ADD_NICE_FOR_DOT = false;
|
||||
|
||||
// static public final boolean USE_IF_VERTICAL = true;
|
||||
|
@ -66,11 +66,11 @@ import net.sourceforge.plantuml.eggs.PSystemColorsFactory;
|
||||
import net.sourceforge.plantuml.eggs.PSystemEggFactory;
|
||||
import net.sourceforge.plantuml.eggs.PSystemPathFactory;
|
||||
import net.sourceforge.plantuml.eggs.PSystemRIPFactory;
|
||||
import net.sourceforge.plantuml.eggs.PSystemUnsupported;
|
||||
import net.sourceforge.plantuml.eggs.PSystemWelcomeFactory;
|
||||
import net.sourceforge.plantuml.emoji.PSystemListEmojiFactory;
|
||||
import net.sourceforge.plantuml.error.PSystemError;
|
||||
import net.sourceforge.plantuml.error.PSystemErrorUtils;
|
||||
import net.sourceforge.plantuml.error.PSystemUnsupported;
|
||||
import net.sourceforge.plantuml.filesdiagram.FilesDiagramFactory;
|
||||
import net.sourceforge.plantuml.flowdiagram.FlowDiagramFactory;
|
||||
import net.sourceforge.plantuml.font.PSystemListFontsFactory;
|
||||
|
@ -33,7 +33,7 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.eggs;
|
||||
package net.sourceforge.plantuml.error;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
@ -119,11 +119,6 @@ public class UGraphicG2d extends AbstractUGraphic<Graphics2D> implements EnsureV
|
||||
|
||||
public UGraphicG2d(HColor defaultBackground, ColorMapper colorMapper, StringBounder stringBounder, Graphics2D g2d,
|
||||
double dpiFactor, FileFormat format) {
|
||||
this(defaultBackground, colorMapper, stringBounder, g2d, dpiFactor, 0, 0, format);
|
||||
}
|
||||
|
||||
public UGraphicG2d(HColor defaultBackground, ColorMapper colorMapper, StringBounder stringBounder, Graphics2D g2d,
|
||||
double dpiFactor, double dx, double dy, FileFormat format) {
|
||||
super(stringBounder);
|
||||
copy(defaultBackground, colorMapper, g2d);
|
||||
this.format = format;
|
||||
|
@ -35,23 +35,20 @@
|
||||
*/
|
||||
package net.sourceforge.plantuml.png;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.sourceforge.plantuml.klimt.color.ColorMapper;
|
||||
import net.sourceforge.plantuml.quantization.Quantizer;
|
||||
import net.sourceforge.plantuml.security.SFile;
|
||||
import net.sourceforge.plantuml.security.SImageIO;
|
||||
import net.sourceforge.plantuml.utils.Log;
|
||||
|
||||
public class PngIO {
|
||||
// ::remove folder when __HAXE__
|
||||
// ::remove folder when __HAXE__
|
||||
|
||||
// ::comment when __CORE__
|
||||
private static final String copyleft = "Generated by https://plantuml.com";
|
||||
public static boolean USE_QUANTIZATION = false;
|
||||
|
||||
public static void write(RenderedImage image, ColorMapper mapper, SFile file, String metadata, int dpi)
|
||||
throws IOException {
|
||||
@ -76,9 +73,6 @@ public class PngIO {
|
||||
String debugData) throws IOException {
|
||||
|
||||
// ::comment when __CORE__
|
||||
if (USE_QUANTIZATION)
|
||||
image = Quantizer.quantizeNow(mapper, (BufferedImage) image);
|
||||
|
||||
if (metadata == null)
|
||||
// ::done
|
||||
SImageIO.write(image, "png", os);
|
||||
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Provides a stable ordering of objects, such that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>compare(a, b) == 0 iff a == b</li>
|
||||
* <li>sign(compare(a, b)) == -sign(compare(b, a))</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Similar to Guava's {code Ordering.arbitrary()}.
|
||||
*/
|
||||
final class ArbitraryComparator implements Comparator<Object> {
|
||||
public static final ArbitraryComparator INSTANCE = new ArbitraryComparator();
|
||||
|
||||
/**
|
||||
* If we have no other way to order two objects in a stable manner, we will
|
||||
* register both in this map and order them according to their associated
|
||||
* values. The map's values are just integers corresponding to the order in
|
||||
* which objects were added.
|
||||
*/
|
||||
private static final WeakHashMap<Object, Integer> objectIds = new WeakHashMap<>();
|
||||
|
||||
private ArbitraryComparator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Object a, Object b) {
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (a == null)
|
||||
return -1;
|
||||
if (b == null)
|
||||
return 1;
|
||||
|
||||
int identityHashCodeDifference = System.identityHashCode(a) - System.identityHashCode(b);
|
||||
if (identityHashCodeDifference != 0) {
|
||||
return identityHashCodeDifference;
|
||||
}
|
||||
|
||||
// We have an identityHashCode collision.
|
||||
return getObjectId(a) - getObjectId(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of an object, adding it to the ID map if it isn't already
|
||||
* registered.
|
||||
*/
|
||||
private static int getObjectId(Object object) {
|
||||
synchronized (objectIds) {
|
||||
Integer id = objectIds.get(object);
|
||||
if (id == null) {
|
||||
id = objectIds.size();
|
||||
objectIds.put(object, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface ColorQuantizer {
|
||||
/**
|
||||
* Quantize the given set of colors, returning a set no larger than
|
||||
* {@code maxColors}.
|
||||
*
|
||||
* <p>
|
||||
* The intent is to pick a set of colors which are representative of the
|
||||
* original color set, but no specific guarantees are made.
|
||||
*
|
||||
* @param originalColors the colors in the original image
|
||||
* @param maxColorCount the maximum number of colors to allow
|
||||
* @return a quantized collection of colors no larger than {@code maxColors}
|
||||
*/
|
||||
public Set<QColor> quantize(Multiset<QColor> originalColors, int maxColorCount);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface Ditherer {
|
||||
// ::remove folder when __HAXE__
|
||||
/**
|
||||
* Dither the given image, producing a new image which only contains colors from
|
||||
* the given color set.
|
||||
*
|
||||
* @param image the original, unquantized image
|
||||
* @param newColors the quantized set of colors to be used in the new image
|
||||
* @return a new image containing only of colors from {@code newColors}
|
||||
*/
|
||||
public QImage dither(QImage image, Set<QColor> newColors);
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public final class FloydSteinbergDitherer implements Ditherer {
|
||||
public static final FloydSteinbergDitherer INSTANCE = new FloydSteinbergDitherer();
|
||||
|
||||
private static final ErrorComponent[] ERROR_DISTRIBUTION = { new ErrorComponent(1, 0, 7.0 / 16.0),
|
||||
new ErrorComponent(-1, 1, 3.0 / 16.0), new ErrorComponent(0, 1, 5.0 / 16.0),
|
||||
new ErrorComponent(1, 1, 1.0 / 16.0) };
|
||||
|
||||
private FloydSteinbergDitherer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public QImage dither(QImage image, Set<QColor> newColors) {
|
||||
final int width = image.getWidth();
|
||||
final int height = image.getHeight();
|
||||
QColor[][] colors = new QColor[height][width];
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x)
|
||||
colors[y][x] = image.getColor(x, y);
|
||||
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x) {
|
||||
final QColor originalColor = colors[y][x];
|
||||
final QColor replacementColor = originalColor.getNearestColor(newColors);
|
||||
colors[y][x] = replacementColor;
|
||||
final QColor error = originalColor.minus(replacementColor);
|
||||
|
||||
for (ErrorComponent component : ERROR_DISTRIBUTION) {
|
||||
int siblingX = x + component.deltaX, siblingY = y + component.deltaY;
|
||||
if (siblingX >= 0 && siblingY >= 0 && siblingX < width && siblingY < height) {
|
||||
QColor errorComponent = error.scaled(component.errorFraction);
|
||||
colors[siblingY][siblingX] = colors[siblingY][siblingX].plus(errorComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QImage.fromColors(colors);
|
||||
}
|
||||
|
||||
private static final class ErrorComponent {
|
||||
final int deltaX, deltaY;
|
||||
final double errorFraction;
|
||||
|
||||
ErrorComponent(int deltaX, int deltaY, double errorFraction) {
|
||||
this.deltaX = deltaX;
|
||||
this.deltaY = deltaY;
|
||||
this.errorFraction = errorFraction;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
public final class HashMultiset<E> extends AbstractCollection<E> implements Multiset<E> {
|
||||
private final Map<E, Count> elementCounts = new HashMap<>();
|
||||
private int size;
|
||||
|
||||
public HashMultiset() {
|
||||
}
|
||||
|
||||
public HashMultiset(Collection<E> source) {
|
||||
addAll(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(E element, int n) {
|
||||
Count count = elementCounts.get(element);
|
||||
if (count != null)
|
||||
count.value += n;
|
||||
else
|
||||
elementCounts.put(element, new Count(n));
|
||||
|
||||
size += n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E element) {
|
||||
add(element, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int remove(Object element, int n) {
|
||||
Count count = elementCounts.get(element);
|
||||
if (count == null)
|
||||
return 0;
|
||||
|
||||
if (n < count.value) {
|
||||
count.value -= n;
|
||||
size -= n;
|
||||
return n;
|
||||
}
|
||||
|
||||
elementCounts.remove(element);
|
||||
size -= count.value;
|
||||
return count.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object element) {
|
||||
return remove(element, 1) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new HashMultisetIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count(Object element) {
|
||||
Count countOrNull = elementCounts.get(element);
|
||||
return countOrNull != null ? countOrNull.value : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<E> getDistinctElements() {
|
||||
return elementCounts.keySet();
|
||||
}
|
||||
|
||||
private final class HashMultisetIterator implements Iterator<E> {
|
||||
final private Iterator<Map.Entry<E, Count>> distinctElementIterator;
|
||||
private E currentElement;
|
||||
private int currentCount;
|
||||
private boolean currentElementRemoved;
|
||||
|
||||
HashMultisetIterator() {
|
||||
this.distinctElementIterator = elementCounts.entrySet().iterator();
|
||||
this.currentCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentCount > 0 || distinctElementIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
if (hasNext() == false)
|
||||
throw new NoSuchElementException("iterator has been exhausted");
|
||||
|
||||
if (currentCount == 0) {
|
||||
Map.Entry<E, Count> next = distinctElementIterator.next();
|
||||
currentElement = next.getKey();
|
||||
currentCount = next.getValue().value;
|
||||
}
|
||||
|
||||
currentCount--;
|
||||
currentElementRemoved = false;
|
||||
return currentElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (currentElement == null)
|
||||
throw new IllegalStateException("next() has not been called");
|
||||
|
||||
if (currentElementRemoved)
|
||||
throw new IllegalStateException("remove() already called for current element");
|
||||
|
||||
HashMultiset.this.remove(currentElement);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Count {
|
||||
private int value;
|
||||
|
||||
Count(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Uses k-means clustering for color quantization. This tends to yield good
|
||||
* results, but convergence can be slow. It is not recommended for large images.
|
||||
*/
|
||||
public final class KMeansQuantizer implements ColorQuantizer {
|
||||
public static final KMeansQuantizer INSTANCE = new KMeansQuantizer();
|
||||
|
||||
private KMeansQuantizer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<QColor> quantize(Multiset<QColor> originalColors, int maxColorCount) {
|
||||
Map<QColor, Multiset<QColor>> clustersByCentroid = new LinkedHashMap<>();
|
||||
Set<QColor> centroidsToRecompute = getInitialCentroids(originalColors, maxColorCount);
|
||||
for (QColor centroid : centroidsToRecompute)
|
||||
clustersByCentroid.put(centroid, new HashMultiset<QColor>());
|
||||
|
||||
for (QColor color : originalColors.getDistinctElements()) {
|
||||
final int count = originalColors.count(color);
|
||||
clustersByCentroid.get(color.getNearestColor(centroidsToRecompute)).add(color, count);
|
||||
}
|
||||
|
||||
while (centroidsToRecompute.isEmpty() == false) {
|
||||
recomputeCentroids(clustersByCentroid, centroidsToRecompute);
|
||||
centroidsToRecompute.clear();
|
||||
|
||||
Set<QColor> allCentroids = clustersByCentroid.keySet();
|
||||
for (QColor centroid : clustersByCentroid.keySet()) {
|
||||
Multiset<QColor> cluster = clustersByCentroid.get(centroid);
|
||||
for (QColor color : new ArrayList<>(cluster.getDistinctElements())) {
|
||||
QColor newCentroid = color.getNearestColor(allCentroids);
|
||||
if (newCentroid != centroid) {
|
||||
final int count = cluster.count(color);
|
||||
final Multiset<QColor> newCluster = clustersByCentroid.get(newCentroid);
|
||||
|
||||
cluster.remove(color, count);
|
||||
newCluster.add(color, count);
|
||||
|
||||
centroidsToRecompute.add(centroid);
|
||||
centroidsToRecompute.add(newCentroid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clustersByCentroid.keySet();
|
||||
}
|
||||
|
||||
private static void recomputeCentroids(Map<QColor, Multiset<QColor>> clustersByCentroid,
|
||||
Set<QColor> centroidsToRecompute) {
|
||||
for (QColor oldCentroid : centroidsToRecompute) {
|
||||
final Multiset<QColor> cluster = clustersByCentroid.get(oldCentroid);
|
||||
final QColor newCentroid = QColor.getCentroid(cluster);
|
||||
clustersByCentroid.remove(oldCentroid);
|
||||
clustersByCentroid.put(newCentroid, cluster);
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<QColor> getInitialCentroids(Multiset<QColor> originalColors, int maxColorCount) {
|
||||
// We use the Forgy initialization method: choose random colors as initial
|
||||
// cluster centroids.
|
||||
final List<QColor> colorList = new ArrayList<>(originalColors.getDistinctElements());
|
||||
Collections.shuffle(colorList);
|
||||
return new HashSet<>(colorList.subList(0, maxColorCount));
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Implements median cut quantization.
|
||||
*
|
||||
* <p>
|
||||
* The algorithm works as follows:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Begin with one cluster containing all the original colors.</li>
|
||||
* <li>Find the cluster containing the greatest spread along a single color
|
||||
* component (red, green or blue).</li>
|
||||
* <li>Find the median of that color component among colors in the cluster.</li>
|
||||
* <li>Split the cluster into two halves, using that median as a threshold.</li>
|
||||
* <li>Repeat this process until the desired number of clusters is reached.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class MedianCutQuantizer implements ColorQuantizer {
|
||||
public static final MedianCutQuantizer INSTANCE = new MedianCutQuantizer();
|
||||
|
||||
private MedianCutQuantizer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<QColor> quantize(Multiset<QColor> originalColors, int maxColorCount) {
|
||||
TreeSet<Cluster> clusters = new TreeSet<>(new ClusterSpreadComparator());
|
||||
clusters.add(new Cluster(originalColors));
|
||||
|
||||
while (clusters.size() < maxColorCount) {
|
||||
Cluster clusterWithLargestSpread = clusters.pollFirst();
|
||||
clusters.addAll(clusterWithLargestSpread.split());
|
||||
}
|
||||
|
||||
Set<QColor> clusterCentroids = new HashSet<>();
|
||||
for (Cluster cluster : clusters) {
|
||||
clusterCentroids.add(QColor.getCentroid(cluster.colors));
|
||||
}
|
||||
return clusterCentroids;
|
||||
}
|
||||
|
||||
private static final class Cluster {
|
||||
final Multiset<QColor> colors;
|
||||
double largestSpread;
|
||||
int componentWithLargestSpread;
|
||||
|
||||
Cluster(Multiset<QColor> colors) {
|
||||
this.colors = colors;
|
||||
this.largestSpread = -1;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
double componentSpread = getComponentSpread(component);
|
||||
if (componentSpread > largestSpread) {
|
||||
largestSpread = componentSpread;
|
||||
componentWithLargestSpread = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double getComponentSpread(int component) {
|
||||
double min = Double.POSITIVE_INFINITY;
|
||||
double max = Double.NEGATIVE_INFINITY;
|
||||
for (QColor color : colors) {
|
||||
min = Math.min(min, color.getComponent(component));
|
||||
max = Math.max(max, color.getComponent(component));
|
||||
}
|
||||
return max - min;
|
||||
}
|
||||
|
||||
Collection<Cluster> split() {
|
||||
List<QColor> orderedColors = new ArrayList<>(colors);
|
||||
Collections.sort(orderedColors, new ColorComponentComparator(componentWithLargestSpread));
|
||||
int medianIndex = orderedColors.size() / 2;
|
||||
return Arrays.asList(new Cluster(new HashMultiset<>(orderedColors.subList(0, medianIndex))),
|
||||
new Cluster(new HashMultiset<>(orderedColors.subList(medianIndex, orderedColors.size()))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders clusters according to their maximum spread, in descending order.
|
||||
*/
|
||||
static final class ClusterSpreadComparator implements Comparator<Cluster> {
|
||||
@Override
|
||||
public int compare(Cluster a, Cluster b) {
|
||||
double spreadDifference = b.largestSpread - a.largestSpread;
|
||||
if (spreadDifference == 0) {
|
||||
return ArbitraryComparator.INSTANCE.compare(a, b);
|
||||
}
|
||||
return (int) Math.signum(spreadDifference);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders colors according to the value of one particular component, in
|
||||
* ascending order.
|
||||
*/
|
||||
static final class ColorComponentComparator implements Comparator<QColor> {
|
||||
final int component;
|
||||
|
||||
ColorComponentComparator(int component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(QColor a, QColor b) {
|
||||
double componentDifference = a.getComponent(component) - b.getComponent(component);
|
||||
if (componentDifference == 0) {
|
||||
return ArbitraryComparator.INSTANCE.compare(a, b);
|
||||
}
|
||||
return (int) Math.signum(componentDifference);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A collection which permits duplicates, and provides methods adding/removing
|
||||
* several counts of an element.
|
||||
*
|
||||
* @param <E> the element type
|
||||
*/
|
||||
public interface Multiset<E> extends Collection<E> {
|
||||
/**
|
||||
* Add n counts of an element.
|
||||
*
|
||||
* @param element the element to add
|
||||
* @param n how many to add
|
||||
*/
|
||||
public void add(E element, int n);
|
||||
|
||||
/**
|
||||
* Remove up to n counts of an element.
|
||||
*
|
||||
* @param element the element the remove
|
||||
* @param n how many to remove
|
||||
* @return the number of elements removed
|
||||
*/
|
||||
public int remove(Object element, int n);
|
||||
|
||||
public int count(Object element);
|
||||
|
||||
public Set<E> getDistinctElements();
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import net.sourceforge.plantuml.klimt.color.ColorMapper;
|
||||
|
||||
/**
|
||||
* An RGB representation of a color, which stores each component as a double in
|
||||
* the range [0, 1]. Values outside of [0, 1] are permitted though, as this is
|
||||
* convenient e.g. for representing color deltas.
|
||||
*/
|
||||
public final class QColor {
|
||||
public static final QColor BLACK = new QColor(0, 0, 0);
|
||||
public static final QColor WHITE = new QColor(1, 1, 1);
|
||||
public static final QColor RED = new QColor(1, 0, 0);
|
||||
public static final QColor GREEN = new QColor(0, 1, 0);
|
||||
public static final QColor BLUE = new QColor(0, 0, 1);
|
||||
|
||||
private final double red;
|
||||
private final double green;
|
||||
private final double blue;
|
||||
|
||||
public QColor(double red, double green, double blue) {
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
}
|
||||
|
||||
public static QColor fromArgbInt(ColorMapper mapper, int rgb) {
|
||||
final double alpha = (rgb >>> 24 & 0xFF) / 255.0;
|
||||
double red = (rgb >>> 16 & 0xFF) / 255.0;
|
||||
double green = (rgb >>> 8 & 0xFF) / 255.0;
|
||||
double blue = (rgb & 0xFF) / 255.0;
|
||||
|
||||
if (mapper == ColorMapper.DARK_MODE) {
|
||||
red = alpha * red;
|
||||
green = alpha * green;
|
||||
blue = alpha * blue;
|
||||
} else {
|
||||
red = 1 - alpha * (1 - red);
|
||||
green = 1 - alpha * (1 - green);
|
||||
blue = 1 - alpha * (1 - blue);
|
||||
}
|
||||
return new QColor(red, green, blue);
|
||||
}
|
||||
|
||||
public static QColor fromRgbInt(int rgb) {
|
||||
final double red = (rgb >>> 16 & 0xFF) / 255.0;
|
||||
final double green = (rgb >>> 8 & 0xFF) / 255.0;
|
||||
final double blue = (rgb & 0xFF) / 255.0;
|
||||
return new QColor(red, green, blue);
|
||||
}
|
||||
|
||||
public static QColor getCentroid(Multiset<QColor> colors) {
|
||||
QColor sum = QColor.BLACK;
|
||||
for (QColor color : colors.getDistinctElements()) {
|
||||
int weight = colors.count(color);
|
||||
sum = sum.plus(color.scaled(weight));
|
||||
}
|
||||
return sum.scaled(1.0 / colors.size());
|
||||
}
|
||||
|
||||
public double getComponent(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return red;
|
||||
case 1:
|
||||
return green;
|
||||
case 2:
|
||||
return blue;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected component index: " + index);
|
||||
}
|
||||
}
|
||||
|
||||
public QColor scaled(double s) {
|
||||
return new QColor(s * red, s * green, s * blue);
|
||||
}
|
||||
|
||||
public QColor plus(QColor that) {
|
||||
return new QColor(this.red + that.red, this.green + that.green, this.blue + that.blue);
|
||||
}
|
||||
|
||||
public QColor minus(QColor that) {
|
||||
return new QColor(this.red - that.red, this.green - that.green, this.blue - that.blue);
|
||||
}
|
||||
|
||||
public double getEuclideanDistanceTo(QColor that) {
|
||||
final QColor d = this.minus(that);
|
||||
final double sumOfSquares = d.red * d.red + d.green * d.green + d.blue * d.blue;
|
||||
return Math.sqrt(sumOfSquares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find this color's nearest neighbor, based on Euclidean distance, among some
|
||||
* set of colors.
|
||||
*/
|
||||
public QColor getNearestColor(Collection<QColor> colors) {
|
||||
QColor nearestCentroid = null;
|
||||
double nearestCentroidDistance = Double.POSITIVE_INFINITY;
|
||||
for (QColor color : colors) {
|
||||
final double distance = getEuclideanDistanceTo(color);
|
||||
if (distance < nearestCentroidDistance) {
|
||||
nearestCentroid = color;
|
||||
nearestCentroidDistance = distance;
|
||||
}
|
||||
}
|
||||
return nearestCentroid;
|
||||
}
|
||||
|
||||
public int getRgbInt() {
|
||||
final int redComponent = (int) (red * 255);
|
||||
final int greenComponent = (int) (green * 255);
|
||||
final int blueComponent = (int) (blue * 255);
|
||||
return redComponent << 16 | greenComponent << 8 | blueComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof QColor))
|
||||
return false;
|
||||
final QColor that = (QColor) o;
|
||||
return this.red == that.red && this.green == that.green && this.blue == that.blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(red);
|
||||
result = (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(green);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(blue);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Color[%f, %f, %f]", red, green, blue);
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import net.sourceforge.plantuml.klimt.color.ColorMapper;
|
||||
|
||||
/**
|
||||
* An immutable grid of pixel colors.
|
||||
*/
|
||||
public final class QImage {
|
||||
/**
|
||||
* The first index corresponds to the row, while the second index corresponds
|
||||
* the column.
|
||||
*/
|
||||
private final QColor[][] colors;
|
||||
|
||||
private QImage(QColor[][] colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public static QImage fromBufferedImage(ColorMapper mapper, BufferedImage img) {
|
||||
final int height = img.getHeight();
|
||||
final int width = img.getWidth();
|
||||
final QColor[][] colors = new QColor[height][width];
|
||||
|
||||
if (img.getType() == BufferedImage.TYPE_INT_ARGB) {
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
colors[y][x] = QColor.fromArgbInt(mapper, img.getRGB(x, y));
|
||||
} else if (img.getType() == BufferedImage.TYPE_INT_RGB) {
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
colors[y][x] = QColor.fromRgbInt(img.getRGB(x, y));
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return new QImage(colors);
|
||||
}
|
||||
|
||||
public static QImage fromColors(QColor[][] colors) {
|
||||
return new QImage(colors);
|
||||
}
|
||||
|
||||
public QColor getColor(int x, int y) {
|
||||
return colors[y][x];
|
||||
}
|
||||
|
||||
public QColor getColor(int index) {
|
||||
return colors[index / getWidth()][index % getWidth()];
|
||||
}
|
||||
|
||||
Multiset<QColor> getColors() {
|
||||
final Multiset<QColor> colorCounts = new HashMultiset<>();
|
||||
for (int i = 0; i < getNumPixels(); ++i) {
|
||||
final QColor color = getColor(i);
|
||||
colorCounts.add(color);
|
||||
}
|
||||
return colorCounts;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return colors[0].length;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return colors.length;
|
||||
}
|
||||
|
||||
public int getNumPixels() {
|
||||
return getWidth() * getHeight();
|
||||
}
|
||||
|
||||
public BufferedImage toBufferedImage() {
|
||||
final BufferedImage result = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
for (int i = 0; i < result.getWidth(); i++)
|
||||
for (int j = 0; j < result.getHeight(); j++)
|
||||
result.setRGB(i, j, colors[j][i].getRgbInt());
|
||||
return result;
|
||||
}
|
||||
|
||||
public BufferedImage toBufferedImageKeepTransparency(BufferedImage orig) {
|
||||
final BufferedImage result = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
for (int i = 0; i < result.getWidth(); i++)
|
||||
for (int j = 0; j < result.getHeight(); j++) {
|
||||
if ((orig.getRGB(i, j)) != 0x00000000)
|
||||
result.setRGB(i, j, colors[j][i].getRgbInt() | 0xFF000000);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package net.sourceforge.plantuml.quantization;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.plantuml.klimt.color.ColorMapper;
|
||||
|
||||
public final class Quantizer {
|
||||
// ::remove folder when __CORE__
|
||||
private static final int MAX_COLOR_COUNT = 256;
|
||||
|
||||
private static QImage quantizeNow(QImage image) throws IOException {
|
||||
|
||||
Multiset<QColor> originalColors = image.getColors();
|
||||
Set<QColor> distinctColors = originalColors.getDistinctElements();
|
||||
if (distinctColors.size() > MAX_COLOR_COUNT) {
|
||||
// distinctColors = KMeansQuantizer.INSTANCE.quantize(originalColors,
|
||||
// MAX_COLOR_COUNT);
|
||||
distinctColors = MedianCutQuantizer.INSTANCE.quantize(originalColors, MAX_COLOR_COUNT);
|
||||
image = FloydSteinbergDitherer.INSTANCE.dither(image, distinctColors);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
public static BufferedImage quantizeNow(ColorMapper mapper, BufferedImage orig) throws IOException {
|
||||
final QImage raw = QImage.fromBufferedImage(mapper, orig);
|
||||
final QImage result = quantizeNow(raw);
|
||||
|
||||
if (orig.getType() == BufferedImage.TYPE_INT_RGB)
|
||||
return result.toBufferedImage();
|
||||
else if (orig.getType() == BufferedImage.TYPE_INT_ARGB)
|
||||
return result.toBufferedImageKeepTransparency(orig);
|
||||
else
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2024, Arnaud Roques
|
||||
*
|
||||
* Project Info: https://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* https://plantuml.com/patreon (only 1$ per month!)
|
||||
* https://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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.
|
||||
*
|
||||
* PlantUML 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.syntax;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.sourceforge.plantuml.BlockUml;
|
||||
import net.sourceforge.plantuml.ErrorUml;
|
||||
import net.sourceforge.plantuml.OptionFlags;
|
||||
import net.sourceforge.plantuml.SourceStringReader;
|
||||
import net.sourceforge.plantuml.UmlDiagram;
|
||||
import net.sourceforge.plantuml.annotation.DeadCode;
|
||||
import net.sourceforge.plantuml.core.Diagram;
|
||||
import net.sourceforge.plantuml.error.PSystemError;
|
||||
import net.sourceforge.plantuml.preproc.Defines;
|
||||
import net.sourceforge.plantuml.text.BackSlash;
|
||||
import net.sourceforge.plantuml.utils.LineLocation;
|
||||
import net.sourceforge.plantuml.utils.LineLocationImpl;
|
||||
|
||||
@DeadCode(comment = "used too much CPU")
|
||||
public class SyntaxChecker {
|
||||
// ::remove folder when __HAXE__
|
||||
// ::remove file when __CORE__
|
||||
|
||||
public static SyntaxResult checkSyntax(List<String> source) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (String s : source) {
|
||||
sb.append(s);
|
||||
sb.append(BackSlash.NEWLINE);
|
||||
}
|
||||
return checkSyntax(sb.toString());
|
||||
}
|
||||
|
||||
public static SyntaxResult checkSyntax(String source) {
|
||||
OptionFlags.getInstance().setQuiet(true);
|
||||
final SyntaxResult result = new SyntaxResult();
|
||||
|
||||
if (source.startsWith("@startuml\n") == false) {
|
||||
result.setError(true);
|
||||
result.setLineLocation(new LineLocationImpl("", null).oneLineRead());
|
||||
result.addErrorText("No @startuml/@enduml found");
|
||||
return result;
|
||||
}
|
||||
if (source.endsWith("@enduml\n") == false && source.endsWith("@enduml") == false) {
|
||||
result.setError(true);
|
||||
result.setLineLocation(lastLineNumber2(source));
|
||||
result.addErrorText("No @enduml found");
|
||||
return result;
|
||||
}
|
||||
final SourceStringReader sourceStringReader = new SourceStringReader(Defines.createEmpty(), source,
|
||||
Collections.<String>emptyList());
|
||||
|
||||
final List<BlockUml> blocks = sourceStringReader.getBlocks();
|
||||
if (blocks.size() == 0) {
|
||||
result.setError(true);
|
||||
result.setLineLocation(lastLineNumber2(source));
|
||||
result.addErrorText("No @enduml found");
|
||||
return result;
|
||||
}
|
||||
final Diagram system = blocks.get(0).getDiagram();
|
||||
result.setCmapData(system.hasUrl());
|
||||
if (system instanceof UmlDiagram) {
|
||||
result.setUmlDiagramType(((UmlDiagram) system).getUmlDiagramType());
|
||||
result.setDescription(system.getDescription().getDescription());
|
||||
} else if (system instanceof PSystemError) {
|
||||
result.setError(true);
|
||||
final PSystemError sys = (PSystemError) system;
|
||||
result.setLineLocation(sys.getLineLocation());
|
||||
result.setSystemError(sys);
|
||||
for (ErrorUml er : sys.getErrorsUml())
|
||||
result.addErrorText(er.getError());
|
||||
} else {
|
||||
result.setDescription(system.getDescription().getDescription());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SyntaxResult checkSyntaxFair(String source) {
|
||||
final SyntaxResult result = new SyntaxResult();
|
||||
final SourceStringReader sourceStringReader = new SourceStringReader(Defines.createEmpty(), source,
|
||||
Collections.<String>emptyList());
|
||||
|
||||
final List<BlockUml> blocks = sourceStringReader.getBlocks();
|
||||
if (blocks.size() == 0) {
|
||||
result.setError(true);
|
||||
result.setLineLocation(lastLineNumber2(source));
|
||||
result.addErrorText("No @enduml found");
|
||||
return result;
|
||||
}
|
||||
|
||||
final Diagram system = blocks.get(0).getDiagram();
|
||||
result.setCmapData(system.hasUrl());
|
||||
if (system instanceof UmlDiagram) {
|
||||
result.setUmlDiagramType(((UmlDiagram) system).getUmlDiagramType());
|
||||
result.setDescription(system.getDescription().getDescription());
|
||||
} else if (system instanceof PSystemError) {
|
||||
result.setError(true);
|
||||
final PSystemError sys = (PSystemError) system;
|
||||
result.setLineLocation(sys.getLineLocation());
|
||||
for (ErrorUml er : sys.getErrorsUml()) {
|
||||
result.addErrorText(er.getError());
|
||||
}
|
||||
result.setSystemError(sys);
|
||||
} else {
|
||||
result.setDescription(system.getDescription().getDescription());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int lastLineNumber(String source) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < source.length(); i++)
|
||||
if (source.charAt(i) == '\n')
|
||||
result++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static LineLocation lastLineNumber2(String source) {
|
||||
LineLocationImpl result = new LineLocationImpl("", null).oneLineRead();
|
||||
for (int i = 0; i < source.length(); i++)
|
||||
if (source.charAt(i) == '\n')
|
||||
result = result.oneLineRead();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user