0

Is there some way to convert a BufferedImage to X11 Pixmap? It looks like JNA has com.sun.jna.platform.unix.X11.Pixmap but I can't find a way to convert anything to that format.

2
  • 1
    github.com/… is not much, but may give an idea to start. Commented Mar 2, 2024 at 14:35
  • Thanks, that got me unstuck from just looking for XPixmap in the source Commented Mar 11, 2024 at 23:52

2 Answers 2

1

The XPM format is pretty simple. You do need to create a colormap, which requires examining every pixel of the image if the image doesn’t use an IndexColorModel.

import java.io.IOException;
import java.io.Writer;

import java.nio.file.Path;
import java.nio.file.Files;

import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

import java.util.stream.IntStream;

import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;

import javax.imageio.ImageIO;

// Reference: https://en.wikipedia.org/wiki/X_PixMap

public class XPixmapConverter {
    public void convertToXPixmap(BufferedImage image,
                                 Writer destination)
    throws IOException {
        int width = image.getWidth();
        int height = image.getHeight();

        int[] rgbValues;

        if (image.getColorModel() instanceof IndexColorModel ic) {
            int size = ic.getMapSize();
            rgbValues = IntStream.range(0, size).map(ic::getRGB).toArray();
        } else {
            Set<Integer> rgb = new HashSet<>();
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    rgb.add(image.getRGB(x, y));
                }
            }

            rgbValues = rgb.stream().mapToInt(Integer::intValue).toArray();
        }

        int colorCount = rgbValues.length;

        String allChars =
            "abcdefghijklmnopqrstuvwxyz" +
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
            "0123456789.-";

        int charsPerPixel = (int)
            Math.ceil(Math.log(colorCount) / Math.log(allChars.length()));

        destination.write("/* XPM */\n");
        destination.write("static char * image[] = {\n");
        destination.write("\"" + width + " " + height + " "
            + colorCount + " " + charsPerPixel + "\",\n");

        Map<Integer, String> pixelReps = new HashMap<>();
        int[] charIndices = new int[charsPerPixel];
        StringBuilder builder = new StringBuilder(charsPerPixel);
        for (int i = 0; i < colorCount; i++) {
            int rgb = rgbValues[i];

            builder.setLength(0);
            for (int j = 0; j < charsPerPixel; j++) {
                builder.append(allChars.charAt(charIndices[j]));
            }
            for (int j = 0; j < charsPerPixel; j++) {
                if (++charIndices[j] < allChars.length()) {
                    break;
                }
                charIndices[j] = 0;
            }

            String pixelRep = builder.toString();
            pixelReps.put(rgb, pixelRep);

            boolean transparent = (rgb >>> 24) == 0;
            destination.write("\"" + pixelRep + " c "
                + (transparent ? "None" :
                    String.format("#%06x", rgb & 0xffffff))
                + "\",\n");
        }

        for (int y = 0; y < height; y++) {
            destination.write("\"");
            for (int x = 0; x < width; x++) {
                destination.write(pixelReps.get(image.getRGB(x, y)));
            }
            destination.write("\"");
            if (y < height - 1) {
                destination.write(",");
            }
            destination.write("\n");
        }

        destination.write("};");
    }

    public void convertToXPixmap(BufferedImage image,
                                 Path destination)
    throws IOException {
        try (Writer writer = Files.newBufferedWriter(destination)) {
            convertToXPixmap(image, writer);
        }
    }

    public static void main(String[] args)
    throws IOException {
        XPixmapConverter converter = new XPixmapConverter();

        for (String arg : args) {
            Path source = Path.of(arg);
            Path dest = Path.of(arg + ".xpm");

            BufferedImage img = ImageIO.read(source.toFile());
            converter.convertToXPixmap(img, dest);
            System.out.println("Wrote \"" + dest + "\".");
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

While that was brilliant and very helpful, I was looking for a way to get the BI into a Pixmap in memory so it could be sent to the X server. I'll gather up that and post it soon.
0

Turns out the answer is to create a BufferedImage with the correct type, create a com.sun.jna.Memory buffer, create an XImage based on that buffer, and then write the BufferedImage's data into the buffer.

The correct type is based on the target visual of the XImage.

I wrote up a demo in Clojure and might still be playing with it. It should be easily conveted back to Java code for anyone interested.

https://github.com/robbieh/clj-xscreensaver-basic-demo/tree/dev

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.