Установите альфа-маску BufferedImage в Java

У меня есть два BufferedImages, загруженные из png. Первый содержит изображение, второе - альфа-маску для изображения.

Я хочу создать комбинированное изображение из двух, применив альфа-маску. Мой google-fu не дает мне возможности.

Я знаю, как загружать/сохранять изображения, мне просто нужен бит, где я перехожу от двух BufferedImages к одному BufferedImage с правильным альфа-каналом.

Ответ 1

Ваше решение может быть улучшено за счет получения данных RGB более одного пикселя за раз (см. http://java.sun.com/javase/6/docs/api/java/awt/image/BufferedImage.html) и не создавая три цветовых объекта на каждой итерации внутреннего цикла.

final int width = image.getWidth();
int[] imgData = new int[width];
int[] maskData = new int[width];

for (int y = 0; y < image.getHeight(); y++) {
    // fetch a line of data from each image
    image.getRGB(0, y, width, 1, imgData, 0, 1);
    mask.getRGB(0, y, width, 1, maskData, 0, 1);
    // apply the mask
    for (int x = 0; x < width; x++) {
        int color = imgData[x] & 0x00FFFFFF; // mask away any alpha present
        int maskColor = (maskData[x] & 0x00FF0000) << 8; // shift red into alpha bits
        color |= maskColor;
        imgData[x] = color;
    }
    // replace the data
    image.setRGB(0, y, width, 1, imgData, 0, 1);
}

Ответ 2

Я опоздал с этим ответом, но, возможно, он кому-то полезен. Это более простая и эффективная версия метода Майкла Майерса:

public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
    int width = image.getWidth();
    int height = image.getHeight();

    int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);

    for (int i = 0; i < imagePixels.length; i++)
    {
        int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
        int alpha = maskPixels[i] << 24; // Shift blue to alpha
        imagePixels[i] = color | alpha;
    }

    image.setRGB(0, 0, width, height, imagePixels, 0, width);
}

Он считывает все пиксели в массив в начале, что требует только одного цикла for. Кроме того, он напрямую сдвигает синий байт на альфу (цвета маски), вместо того, чтобы сначала маскировать красный байт, а затем перемещать его.

Как и другие методы, предполагается, что оба изображения имеют одинаковые размеры.

Ответ 3

Недавно я немного поиграл с этим материалом, чтобы отобразить изображение над другим, и чтобы изображение стало серым. Также маскирование изображения с маской с прозрачностью (моя предыдущая версия этого сообщения!).

Я взял маленькую тестовую программу и немного изменил ее, чтобы получить желаемый результат.

Вот соответствующие бит:

TestMask() throws IOException
{
    m_images = new BufferedImage[3];
    m_images[0] = ImageIO.read(new File("E:/Documents/images/map.png"));
    m_images[1] = ImageIO.read(new File("E:/Documents/images/mapMask3.png"));
    Image transpImg = TransformGrayToTransparency(m_images[1]);
    m_images[2] = ApplyTransparency(m_images[0], transpImg);
}

private Image TransformGrayToTransparency(BufferedImage image)
{
    ImageFilter filter = new RGBImageFilter()
    {
        public final int filterRGB(int x, int y, int rgb)
        {
            return (rgb << 8) & 0xFF000000;
        }
    };

    ImageProducer ip = new FilteredImageSource(image.getSource(), filter);
    return Toolkit.getDefaultToolkit().createImage(ip);
}

private BufferedImage ApplyTransparency(BufferedImage image, Image mask)
{
    BufferedImage dest = new BufferedImage(
            image.getWidth(), image.getHeight(),
            BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = dest.createGraphics();
    g2.drawImage(image, 0, 0, null);
    AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0F);
    g2.setComposite(ac);
    g2.drawImage(mask, 0, 0, null);
    g2.dispose();
    return dest;
}

Остальная часть просто отображает изображения на маленькой панели Swing.
Обратите внимание, что изображение маски имеет уровень серого, черный становится полной прозрачностью, белый становится полным непрозрачным.

Несмотря на то, что вы решили свою проблему, я мог бы поделиться своим делом. Он использует немного больше Java-ish-метода, используя стандартные классы для обработки/фильтрации изображений.
На самом деле, мой метод использует немного больше памяти (создание дополнительного изображения), и я не уверен, что он быстрее (измерение соответствующих характеристик может быть интересно), но это немного более абстрактно. По крайней мере, у вас есть выбор!: -)

Ответ 4

Собственно, я понял это. Это, вероятно, не быстрый способ сделать это, но он работает:

for (int y = 0; y < image.getHeight(); y++) {
    for (int x = 0; x < image.getWidth(); x++) {
        Color c = new Color(image.getRGB(x, y));
        Color maskC = new Color(mask.getRGB(x, y));
        Color maskedColor = new Color(c.getRed(), c.getGreen(), c.getBlue(),
            maskC.getRed());
        resultImg.setRGB(x, y, maskedColor.getRGB());
    }
}

Ответ 5

Для тех, кто использует альфа в исходном изображении.

Я написал этот код в Koltin, ключевым моментом здесь является то, что если у вас есть альфа на вашем исходном изображении, вам нужно умножить эти каналы.

Версия Колтина:

    val width = this.width
    val imgData = IntArray(width)
    val maskData = IntArray(width)

    for(y in 0..(this.height - 1)) {

      this.getRGB(0, y, width, 1, imgData, 0, 1)
      mask.getRGB(0, y, width, 1, maskData, 0, 1)

      for (x in 0..(this.width - 1)) {

        val maskAlpha = (maskData[x] and 0x000000FF)/ 255f
        val imageAlpha = ((imgData[x] shr 24) and 0x000000FF) / 255f
        val rgb = imgData[x] and 0x00FFFFFF
        val alpha = ((maskAlpha * imageAlpha) * 255).toInt() shl 24
        imgData[x] = rgb or alpha
      }
      this.setRGB(0, y, width, 1, imgData, 0, 1)
    }

версия Java (только что переведенная с Kotlin)

    int width = image.getWidth();
    int[] imgData = new int[width];
    int[] maskData = new int[width];

    for (int y = 0; y < image.getHeight(); y ++) {

        image.getRGB(0, y, width, 1, imgData, 0, 1);
        mask.getRGB(0, y, width, 1, maskData, 0, 1);

        for (int x = 0; x < image.getWidth(); x ++) {

            //Normalize (0 - 1)
            float maskAlpha = (maskData[x] & 0x000000FF)/ 255f;
            float imageAlpha = ((imgData[x] >> 24) & 0x000000FF) / 255f;

            //Image without alpha channel
            int rgb = imgData[x] & 0x00FFFFFF;

            //Multiplied alpha
            int alpha = ((int) ((maskAlpha * imageAlpha) * 255)) << 24;

            //Add alpha to image
            imgData[x] = rgb | alpha;
        }
        image.setRGB(0, y, width, 1, imgData, 0, 1);
    }