/*
BitmapModeConverter.java
(c) 2016 Edward Swartz
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
*/
package v9t9.video.imageimport;
import java.awt.image.BufferedImage;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.ejs.gui.images.ColorMapUtils;
import org.ejs.gui.images.FixedPaletteMapColor;
import org.ejs.gui.images.IPaletteColorMapper;
import org.ejs.gui.images.IPaletteMapper;
import v9t9.common.video.VdpColorManager;
import v9t9.video.imageimport.ImageImportOptions.Dither;
import ejs.base.utils.Pair;
/**
* @author ejs
*
*/
public class BitmapModeConverter extends BaseBitmapModeConverter {
private Dither ditherType;
private byte[][] thePalette;
private int firstColor;
public BitmapModeConverter(VdpColorManager colorMgr, boolean useColorMappedGreyScale, Dither ditherType,
IPaletteMapper mapColor, byte[][] thePalette, int firstColor) {
super(8, colorMgr, useColorMappedGreyScale, mapColor);
this.ditherType = ditherType;
this.thePalette = thePalette;
this.firstColor = firstColor;
}
/* (non-Javadoc)
* @see v9t9.video.imageimport.IModeConverter#prepareImage(java.awt.image.BufferedImage)
*/
@Override
public BufferedImage prepareImage(BufferedImage img) {
return img;
}
/**
* Only two colors can exist per 8x1 pixels, so find those colors.
* If there's a tossup (lots of colors), use information from neighbors
* to enhance the odds.
* @param img
* @param xoffs
* @param yoffs
*/
protected void reduceBitmapMode(final BufferedImage convertedImage, final BufferedImage img, final int xoffs, final int yoffs) {
analyzeBitmap(img, 4, new IBitmapColorUser() {
@Override
public void useColor(int x, int maxx, int y,
List<Pair<Integer,Integer>> sorted) {
int fpixel, bpixel;
if (sorted.size() <= 2) {
// ok, only two different colors to care about -- fast path
fpixel = sorted.get(0).first;
bpixel = sorted.size() > 1 ? sorted.get(1).first : fpixel;
int origPixel = 0;
for (int xd = x; xd < maxx; xd++) {
if (xd < img.getWidth()) {
origPixel = img.getRGB(xd, y);
if (useColorMappedGreyScale)
origPixel = mapColor.getPixelForGreyscaleMode(origPixel);
}
int fdist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(origPixel, fpixel)
: ColorMapUtils.getPixelDistance(origPixel, fpixel);
int bdist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(origPixel, bpixel) :
ColorMapUtils.getPixelDistance(origPixel, bpixel);
int newPixel;
if (fdist < bdist) {
newPixel = fpixel;
} else {
newPixel = bpixel;
}
convertedImage.setRGB(xd + xoffs, y + yoffs, newPixel);
}
} else {
// there are more than 2 significant colors:
// since only two will appear in this 8 pixels,
// find the most representative
int total = 0;
for (Pair<Integer, Integer> ent : sorted) {
total += ent.second;
}
boolean dither = false;
if (true) {
fpixel = sorted.get(0).first;
int fcnt = sorted.get(0).second;
bpixel = sorted.get(1).first;
int bcnt = sorted.get(1).second;
int fbDist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(fpixel, bpixel)
: ColorMapUtils.getPixelDistance(fpixel, bpixel);
int wantDist = useColorMappedGreyScale ? 0x3f*0x3f : 0x3f*0x3f * 3;
if (fcnt + bcnt < total * 8 / 10 && fbDist < wantDist) {
for (int sidx = 2; sidx < sorted.size(); sidx++) {
Pair<Integer, Integer> ent = sorted.get(sidx);
if (ent.second < 2)
break;
int dist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(fpixel, ent.first)
: ColorMapUtils.getPixelDistance(fpixel, ent.first);
if (dist > fbDist && dist > wantDist) {
bpixel = ent.first;
fbDist = dist;
}
}
}
if (ditherType != Dither.NONE && fbDist > wantDist && !useColorMappedGreyScale)
dither = true;
} else {
IPaletteColorMapper mapColor = new FixedPaletteMapColor(thePalette, firstColor, 16);
int[] prgb = { 0, 0, 0 };
Map<Integer, Integer> matches = new LinkedHashMap<Integer, Integer>();
int loss;
for (loss = 0; loss < 8; loss++) {
matches.clear();
for (int i = 0; i < sorted.size(); i++) {
Pair<Integer, Integer> ent = sorted.get(i);
ColorMapUtils.pixelToRGB(ent.first, prgb);
prgb[0] &= 0xff << loss;
prgb[1] &= 0xff << loss;
prgb[2] &= 0xff << loss;
int pixel = ColorMapUtils.rgb8ToPixel(prgb);
int color = mapColor.getClosestPalettePixel(x, y, pixel);
Integer cur = matches.get(pixel);
if (cur == null) {
cur = ent.first;
matches.put(color, cur);
}
}
if (matches.size() <= 2)
break;
}
Iterator<Map.Entry<Integer, Integer>> iterator = matches.entrySet().iterator();
fpixel = iterator.next().getValue();
bpixel = iterator.hasNext() ? iterator.next().getValue() : fpixel;
dither = loss >= 4 && !useColorMappedGreyScale;
}
int[] prgb = { 0, 0, 0 };
int origPixel = 0;
for (int xd = x; xd < maxx; xd++) {
if (xd < img.getWidth()) {
origPixel = img.getRGB(xd, y);
if (useColorMappedGreyScale)
origPixel = mapColor.getPixelForGreyscaleMode(origPixel);
}
int newPixel = origPixel;
if (dither) {
ColorMapUtils.pixelToRGB(origPixel, prgb);
int threshold = DitherOrdered.thresholdMap8x8[xd & 7][y & 7];
prgb[0] = (prgb[0] + threshold - 32);
prgb[1] = (prgb[1] + threshold - 32);
prgb[2] = (prgb[2] + threshold - 32);
newPixel = ColorMapUtils.rgb8ToPixel(prgb);
}
int fdist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(newPixel, fpixel)
: ColorMapUtils.getPixelDistance(newPixel, fpixel);
int bdist = useColorMappedGreyScale ? ColorMapUtils.getPixelLumDistance(newPixel, bpixel) :
ColorMapUtils.getPixelDistance(newPixel, bpixel);
if (fdist < bdist) {
newPixel = fpixel;
} else {
newPixel = bpixel;
}
convertedImage.setRGB(xd + xoffs, y + yoffs, newPixel);
}
}
}
});
}
/* (non-Javadoc)
* @see v9t9.video.imageimport.IModeConverter#convert(java.awt.image.BufferedImage, java.awt.image.BufferedImage, int, int)
*/
@Override
public BufferedImage convert(BufferedImage img,
int targWidth, int targHeight) {
int xoffs, yoffs;
xoffs = (targWidth - img.getWidth()) / 2;
yoffs = (targHeight - img.getHeight()) / 2;
// be sure we select the 8 pixel groups sensibly
if ((xoffs & 7) > 3)
xoffs = (xoffs + 7) & ~7;
else
xoffs = xoffs & ~7;
BufferedImage convertedImage = new BufferedImage(targWidth, targHeight,
BufferedImage.TYPE_3BYTE_BGR);
reduceBitmapMode(convertedImage, img, xoffs, yoffs);
return convertedImage;
}
}