为双色图像着色,求LookupOp的内存高效替代方案
黑白图像颜色替换的LookupOp优化方案
问题背景
现有仅包含黑、白两色的图像(ARGB值分别为0xFF000000和0xFFFFFFFF,无灰色),需将黑色替换为指定颜色,白色替换为另一指定颜色。当前采用LookupOp结合ByteLookupTable实现,但存在以下问题:
- 内存占用高:每个颜色对需创建4×256的字节数组,至少占用1KB内存
- 缓存压力大:20万颜色对缓存占用约200MB内存
- 创建速度慢:构建完整查找表有额外开销
当前实现代码:
private LookupOp createLookupOp(Color foreground, Color background) { var lookupTable = new byte[4][256]; var fg = foreground.getRGB(); Arrays.fill(lookupTable[0], (byte) (fg >> 16)); Arrays.fill(lookupTable[1], (byte) (fg >> 8)); Arrays.fill(lookupTable[2], (byte) (fg)); Arrays.fill(lookupTable[3], (byte) (fg >> 24)); var bg = background.getRGB(); lookupTable[0][0] = (byte) (bg >> 16); lookupTable[1][0] = (byte) (bg >> 8); lookupTable[2][0] = (byte) (bg); lookupTable[3][0] = (byte) (bg >> 24); return new LookupOp(new ByteLookupTable(0, lookupTable), null); }
使用方式:
var lookup = lookups.computeIfAbsent(colorPair, colors -> createLookupOp(colors.foreground, colors.background)); var coloredImage = lookup.filter(originalImage, null); graphics2d.drawImage(coloredImage, originalWidth, originalHeight, null);
优化方案
方案1:自定义BufferedImageOp实现(低内存+高性能)
利用图像仅含黑白两色的特性,直接逐像素判断替换,无需完整查找表。每个实例仅占用8字节内存(两个int变量),20万实例仅约1.6MB内存。
class BinaryColorReplaceOp implements BufferedImageOp { private final int targetBlack; private final int targetWhite; public BinaryColorReplaceOp(Color replaceBlackWith, Color replaceWhiteWith) { this.targetBlack = replaceBlackWith.getRGB(); this.targetWhite = replaceWhiteWith.getRGB(); } @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { if (dest == null) { dest = createCompatibleDestImage(src, null); } int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); for (int i = 0; i < pixels.length; i++) { pixels[i] = (pixels[i] == 0xFF000000) ? targetBlack : targetWhite; } dest.setRGB(0, 0, width, height, pixels, 0, width); return dest; } @Override public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) { if (destCM == null) { destCM = src.getColorModel(); } return new BufferedImage(destCM, destCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), destCM.isAlphaPremultiplied(), null); } @Override public Rectangle2D getBounds2D(BufferedImage src) { return new Rectangle(0, 0, src.getWidth(), src.getHeight()); } @Override public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { return dstPt == null ? new Point2D.Float((float) srcPt.getX(), (float) srcPt.getY()) : dstPt.setLocation(srcPt); } @Override public RenderingHints getRenderingHints() { return null; } }
使用方式:
var op = new BinaryColorReplaceOp(foreground, background); var coloredImage = op.filter(originalImage, null); graphics2d.drawImage(coloredImage, originalWidth, originalHeight, null);
方案2:直接绘制时替换(零内存开销)
如果不需要保存转换后的图像,仅需临时绘制,可通过Graphics2D的合成模式直接实现颜色替换,无需创建任何操作实例:
// 先绘制黑色替换色,仅覆盖原黑色区域 graphics2d.setComposite(AlphaComposite.Src); graphics2d.setColor(background); graphics2d.drawImage(originalImage, 0, 0, originalWidth, originalHeight, null); // 再绘制白色替换色,用原图像作为遮罩(仅白色区域被覆盖) graphics2d.setComposite(AlphaComposite.SrcIn); graphics2d.setColor(foreground); graphics2d.fillRect(0, 0, originalWidth, originalHeight);
该方案完全避免了缓存和图像转换的内存开销,适合实时绘制场景。
方案3:自定义ColorSpace结合ColorConvertOp(支持硬件加速)
若需利用Java2D的硬件加速,可通过自定义ColorSpace实现颜色映射,内存占用同样极低:
class BinaryColorSpace extends ColorSpace { private final int targetBlack; private final int targetWhite; public BinaryColorSpace(Color replaceBlackWith, Color replaceWhiteWith) { super(ColorSpace.TYPE_RGB, 3); this.targetBlack = replaceBlackWith.getRGB(); this.targetWhite = replaceWhiteWith.getRGB(); } @Override public float[] fromRGB(float[] rgbvalue) { return rgbvalue; } @Override public float[] toRGB(float[] colorvalue) { float r = colorvalue[0], g = colorvalue[1], b = colorvalue[2]; if (r == 0 && g == 0 && b == 0) { return new float[]{ ((targetBlack >> 16) & 0xFF) / 255f, ((targetBlack >> 8) & 0xFF) / 255f, (targetBlack & 0xFF) / 255f }; } else { return new float[]{ ((targetWhite >> 16) & 0xFF) / 255f, ((targetWhite >> 8) & 0xFF) / 255f, (targetWhite & 0xFF) / 255f }; } } @Override public float[] fromCIEXYZ(float[] colorvalue) { throw new UnsupportedOperationException(); } @Override public float[] toCIEXYZ(float[] colorvalue) { throw new UnsupportedOperationException(); } }
使用方式:
var colorSpace = new BinaryColorSpace(foreground, background); var op = new ColorConvertOp(colorSpace, null); var coloredImage = op.filter(originalImage, null); graphics2d.drawImage(coloredImage, originalWidth, originalHeight, null);
内容的提问来源于stack exchange,提问作者Olivier Grégoire
相关产品推荐
相关产品推荐

