-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add early implementation of Gaussian MSE char selection algorithm
- Loading branch information
1 parent
535fa3a
commit d323964
Showing
5 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
186 changes: 186 additions & 0 deletions
186
src/main/java/pl/asie/zima/image/GmseImageMseCalculator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
/** | ||
* Copyright (c) 2020, 2021 Adrian Siekierka | ||
* | ||
* This file is part of zima. | ||
* | ||
* zima 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. | ||
* | ||
* zima 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 zima. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package pl.asie.zima.image; | ||
|
||
import pl.asie.libzzt.TextVisualData; | ||
import pl.asie.zima.util.ColorUtils; | ||
import pl.asie.zima.util.DebugUtils; | ||
import pl.asie.zima.util.Gaussian2DKernel; | ||
|
||
import java.awt.image.BufferedImage; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
|
||
public class GmseImageMseCalculator implements ImageMseCalculator { | ||
public static class ColorMixCache { | ||
private final TextVisualData visual; | ||
private final int accuracy; | ||
private final List<int[]> cache; | ||
|
||
public ColorMixCache(TextVisualData visual, int accuracy) { | ||
this.visual = visual; | ||
this.accuracy = accuracy; | ||
this.cache = IntStream.range(0, 256).parallel().mapToObj(col -> { | ||
int bgColor = visual.getPalette()[col >> 4]; | ||
int fgColor = visual.getPalette()[col & 0x0F]; | ||
float accAsFloat = (float) accuracy; | ||
|
||
return IntStream.rangeClosed(0, accuracy).map(mixFactor -> { | ||
return ColorUtils.mix(bgColor, fgColor, mixFactor / accAsFloat); | ||
}).toArray(); | ||
}).collect(Collectors.toList()); | ||
} | ||
|
||
public int mix(int col, float mix) { | ||
return cache.get(col)[Math.round(mix * accuracy)]; | ||
} | ||
} | ||
|
||
public static class GaussianCharCache { | ||
private final TextVisualData visual; | ||
private final List<float[]> cache; | ||
private final Gaussian2DKernel kernel; | ||
|
||
private static int getCharPixelAt(TextVisualData visual, int chr, int cx, int cy) { | ||
if (cx < 0) cx = 0; | ||
else if (cx >= visual.getCharWidth()) cx = visual.getCharWidth() - 1; | ||
if (cy < 0) cy = 0; | ||
else if (cy >= visual.getCharHeight()) cy = visual.getCharHeight() - 1; | ||
return (visual.getCharData()[(chr * 14) + cy] >> (7 - cx)) & 1; | ||
|
||
|
||
/* if (cx >= 0 && cy >= 0 && cx < visual.getCharWidth() && cy < visual.getCharHeight()) { | ||
return (visual.getCharData()[(chr * 14) + cy] >> (7 - cx)) & 1; | ||
} else { | ||
return 0; | ||
} */ | ||
} | ||
|
||
public GaussianCharCache(TextVisualData visual, int radius, float sigma) { | ||
this.visual = visual; | ||
this.kernel = new Gaussian2DKernel(sigma, radius); | ||
this.cache = IntStream.range(0, 256).parallel().mapToObj(chr -> { | ||
float[] data = new float[visual.getCharWidth() * visual.getCharHeight()]; | ||
int ci = 0; | ||
|
||
for (int cy = 0; cy < visual.getCharHeight(); cy++) { | ||
for (int cx = 0; cx < visual.getCharWidth(); cx++) { | ||
float cv = 0.0f; | ||
for (int ky = -radius; ky <= radius; ky++) { | ||
for (int kx = -radius; kx <= radius; kx++) { | ||
int v = getCharPixelAt(visual, chr, cx + kx, cy + ky); | ||
float k = kernel.at(kx, ky); | ||
cv += k * v; | ||
} | ||
} | ||
data[ci++] = cv; | ||
} | ||
} | ||
|
||
return data; | ||
}).collect(Collectors.toCollection(ArrayList::new)); | ||
|
||
for (Map.Entry<Integer, Float> entries : Map.of( | ||
176, 0.25f, | ||
177, 0.5f, | ||
178, 0.75f | ||
).entrySet()) { | ||
float[] data = this.cache.get(entries.getKey()); | ||
Arrays.fill(data, entries.getValue()); | ||
} | ||
|
||
DebugUtils.print1DArray(this.cache.get(178), visual.getCharWidth()); | ||
} | ||
|
||
public float[] getGaussian(int chr) { | ||
return this.cache.get(chr & 0xFF); | ||
} | ||
} | ||
|
||
private final TextVisualData visual; | ||
private final GaussianCharCache gaussCache; | ||
private final ColorMixCache mixCache; | ||
private final float contrastReduction; | ||
private final boolean blinkingDisabled; | ||
|
||
public GmseImageMseCalculator(TextVisualData visual, boolean blinkingDisabled, float contrastReduction) { | ||
this.visual = visual; | ||
this.gaussCache = new GaussianCharCache(visual, 3, 1.5f); | ||
this.mixCache = new ColorMixCache(visual, 256); | ||
this.contrastReduction = contrastReduction; | ||
this.blinkingDisabled = blinkingDisabled; | ||
} | ||
|
||
@Override | ||
public Applier applyMse(BufferedImage image, int px, int py) { | ||
final int[] imgColorLut = new int[visual.getCharWidth() * visual.getCharHeight()]; | ||
{ | ||
int ci = 0; | ||
for (int cy = 0; cy < visual.getCharHeight(); cy++) { | ||
for (int cx = 0; cx < visual.getCharWidth(); cx++, ci++) { | ||
int ix = px + cx; | ||
int iy = py + cy; | ||
imgColorLut[ci] = image.getRGB(ix, iy); | ||
} | ||
} | ||
} | ||
|
||
float maxDistanceTmp = 0.0f; | ||
for (int i = 0; i < imgColorLut.length; i++) { | ||
for (int j = i + 1; j < imgColorLut.length; j++) { | ||
maxDistanceTmp = Math.max(maxDistanceTmp, ColorUtils.distance(imgColorLut[i], imgColorLut[j])); | ||
} | ||
} | ||
float maxDistance = maxDistanceTmp; | ||
|
||
int colorMask = blinkingDisabled ? 0xFF : 0x7F; | ||
|
||
return (proposed, maxMse) -> { | ||
float mse = 0.0f; | ||
|
||
int color = proposed.getColor() & colorMask; | ||
int bgColor = visual.getPalette()[(proposed.getColor() >> 4) & (blinkingDisabled ? 0x0F : 0x07)]; | ||
int fgColor = visual.getPalette()[proposed.getColor() & 0x0F]; | ||
|
||
float contrastDiff = maxDistance - ColorUtils.distance(bgColor, fgColor); | ||
float mseContrastReduction = contrastReduction * contrastDiff * contrastDiff; | ||
mse += mseContrastReduction * imgColorLut.length; | ||
if (mse > maxMse) { | ||
return mse; | ||
} | ||
|
||
float[] charBlurred = gaussCache.getGaussian(proposed.getCharacter()); | ||
for (int ci = 0; ci < imgColorLut.length; ci++) { | ||
int imgColor = imgColorLut[ci]; | ||
//int localCharColor = ColorUtils.mix(bgColor, fgColor, charBlurred[ci]); | ||
int localCharColor = this.mixCache.mix(color, charBlurred[ci]); | ||
mse += ColorUtils.distance(imgColor, localCharColor); | ||
if (mse > maxMse) { | ||
break; | ||
} | ||
} | ||
|
||
return mse; | ||
}; | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
src/main/java/pl/asie/zima/image/NaiveImageMseCalculator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/** | ||
* Copyright (c) 2020, 2021 Adrian Siekierka | ||
* | ||
* This file is part of zima. | ||
* | ||
* zima 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. | ||
* | ||
* zima 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 zima. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package pl.asie.zima.image; | ||
|
||
import pl.asie.libzzt.TextVisualData; | ||
import pl.asie.zima.util.ColorUtils; | ||
|
||
import java.awt.image.BufferedImage; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
public class NaiveImageMseCalculator implements ImageMseCalculator { | ||
private final TextVisualData visual; | ||
private final boolean blinkingDisabled; | ||
|
||
public NaiveImageMseCalculator(TextVisualData visual, boolean blinkingDisabled) { | ||
this.visual = visual; | ||
this.blinkingDisabled = blinkingDisabled; | ||
} | ||
|
||
@Override | ||
public Applier applyMse(BufferedImage image, int px, int py) { | ||
final int[] imgColorLut = new int[visual.getCharWidth() * visual.getCharHeight()]; | ||
for (int cy = 0; cy < visual.getCharHeight(); cy++) { | ||
for (int cx = 0; cx < visual.getCharWidth(); cx++) { | ||
imgColorLut[cy * visual.getCharWidth() + cx] = image.getRGB(px + cx, py + cy); | ||
} | ||
} | ||
|
||
return (proposed, maxMse) -> { | ||
float mse = 0.0f; | ||
|
||
int bgColor = visual.getPalette()[(proposed.getColor() >> 4) & (blinkingDisabled ? 0x07 : 0x0F)]; | ||
int fgColor = visual.getPalette()[proposed.getColor() & 0x0F]; | ||
int charColor = 0; | ||
boolean forcedCharColor = true; | ||
switch (proposed.getCharacter()) { | ||
case 176: | ||
charColor = ColorUtils.mix(fgColor, bgColor, 0.75f); | ||
break; | ||
case 177: | ||
charColor = ColorUtils.mix(fgColor, bgColor, 0.5f); | ||
break; | ||
case 178: | ||
charColor = ColorUtils.mix(fgColor, bgColor, 0.25f); | ||
break; | ||
default: | ||
forcedCharColor = false; | ||
break; | ||
} | ||
|
||
if (!forcedCharColor) { | ||
int coff = proposed.getCharacter() * visual.getCharHeight(); | ||
int ci = 0; | ||
for (int cy = 0; cy < visual.getCharHeight(); cy++) { | ||
int charLine = (int) visual.getCharData()[coff + cy] & 0xFF; | ||
for (int cx = 0; cx < visual.getCharWidth(); cx++) { | ||
int imgColor = imgColorLut[ci++]; | ||
int localCharColor = (charLine & (1 << (7 - cx))) != 0 ? fgColor : bgColor; | ||
mse += ColorUtils.distance(imgColor, localCharColor); | ||
if (mse > maxMse) { | ||
break; | ||
} | ||
} | ||
} | ||
} else { | ||
for (int ci = 0; ci < visual.getCharHeight() * visual.getCharWidth(); ci++) { | ||
int imgColor = imgColorLut[ci++]; | ||
mse += ColorUtils.distance(imgColor, charColor); | ||
if (mse > maxMse) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return mse; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* Copyright (c) 2020, 2021 Adrian Siekierka | ||
* | ||
* This file is part of zima. | ||
* | ||
* zima 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. | ||
* | ||
* zima 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 zima. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package pl.asie.zima.util; | ||
|
||
public final class DebugUtils { | ||
public static final Object PRINT_LOCK = new Object(); | ||
|
||
private DebugUtils() { | ||
|
||
} | ||
|
||
public static void print1DArray(float[] arr, int pitch) { | ||
synchronized (PRINT_LOCK) { | ||
for (int i = 0; i < arr.length; i++) { | ||
if (i > 0 && (i % pitch) == 0) { | ||
System.out.printf("[%d]", i); | ||
System.out.println(); | ||
} | ||
System.out.printf("%.4f\t", arr[i]); | ||
} | ||
System.out.println(); | ||
} | ||
} | ||
} |
Oops, something went wrong.