You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为双色图像着色,求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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 04:37:00