Как объединить несколько PNG в один большой файл PNG? - PullRequest
28 голосов
/ 13 октября 2010

у меня ок. 6000 файлов PNG (256 * 256 пикселей) и хотите объединить их в большой PNG-файл, содержащий все их программно.

Какой самый лучший / быстрый способ сделать это?

(Целью является печать на бумаге, поэтому использование некоторых веб-технологий не является опцией, а наличие единого файла изображения устранит многие ошибки использования.)

Я попробовал предложение Фахда, но получаю NullPointerException, когда пытаюсь создать BufferedImage с шириной 24576 пикселей и высотой 15360 пикселей. Есть идеи?

Ответы [ 9 ]

53 голосов
/ 13 октября 2010

Создайте большое изображение, в которое вы будете писать.Определите его размеры, исходя из того, сколько строк и столбцов вы хотите.

    BufferedImage result = new BufferedImage(
                               width, height, //work these out
                               BufferedImage.TYPE_INT_RGB);
    Graphics g = result.getGraphics();

Теперь переберите ваши изображения и нарисуйте их:

    for(String image : images){
        BufferedImage bi = ImageIO.read(new File(image));
        g.drawImage(bi, x, y, null);
        x += 256;
        if(x > result.getWidth()){
            x = 0;
            y += bi.getHeight();
        }
    }

Наконец запишите это в файл:

    ImageIO.write(result,"png",new File("result.png"));
7 голосов
/ 30 мая 2011

Некоторое время назад у меня была какая-то похожая потребность (огромные изображения - и я в моем случае с 16 битами - чтобы их полностью в памяти не было).И я закончил кодировать библиотеку PNG для последовательного чтения / записи.Если кто-то найдет это полезным, это здесь .

Обновлено: вот пример кода:

/**
 * Takes several tiles and join them in a single image
 * 
 * @param tiles            Filenames of PNG files to tile
 * @param dest            Destination PNG filename
 * @param nTilesX            How many tiles per row?
 */
public class SampleTileImage {

        public static void doTiling(String tiles[], String dest, int nTilesX) {
                int ntiles = tiles.length;
                int nTilesY = (ntiles + nTilesX - 1) / nTilesX; // integer ceil
                ImageInfo imi1, imi2; // 1:small tile   2:big image
                PngReader pngr = new PngReader(new File(tiles[0]));
                imi1 = pngr.imgInfo;
                PngReader[] readers = new PngReader[nTilesX];
                imi2 = new ImageInfo(imi1.cols * nTilesX, imi1.rows * nTilesY, imi1.bitDepth, imi1.alpha, imi1.greyscale,
                                imi1.indexed);
                PngWriter pngw = new PngWriter(new File(dest), imi2, true);
                // copy palette and transparency if necessary (more chunks?)
                pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_PALETTE
                                | ChunkCopyBehaviour.COPY_TRANSPARENCY);
                pngr.readSkippingAllRows(); // reads only metadata             
                pngr.end(); // close, we'll reopen it again soon
                ImageLineInt line2 = new ImageLineInt(imi2);
                int row2 = 0;
                for (int ty = 0; ty < nTilesY; ty++) {
                        int nTilesXcur = ty < nTilesY - 1 ? nTilesX : ntiles - (nTilesY - 1) * nTilesX;
                        Arrays.fill(line2.getScanline(), 0);
                        for (int tx = 0; tx < nTilesXcur; tx++) { // open several readers
                                readers[tx] = new PngReader(new File(tiles[tx + ty * nTilesX]));
                                readers[tx].setChunkLoadBehaviour(ChunkLoadBehaviour.LOAD_CHUNK_NEVER);
                                if (!readers[tx].imgInfo.equals(imi1))
                                        throw new RuntimeException("different tile ? " + readers[tx].imgInfo);
                        }
                        for (int row1 = 0; row1 < imi1.rows; row1++, row2++) {
                                for (int tx = 0; tx < nTilesXcur; tx++) {
                                        ImageLineInt line1 = (ImageLineInt) readers[tx].readRow(row1); // read line
                                        System.arraycopy(line1.getScanline(), 0, line2.getScanline(), line1.getScanline().length * tx,
                                                        line1.getScanline().length);
                                }
                                pngw.writeRow(line2, row2); // write to full image
                        }
                        for (int tx = 0; tx < nTilesXcur; tx++)
                                readers[tx].end(); // close readers
                }
                pngw.end(); // close writer
        }

        public static void main(String[] args) {
                doTiling(new String[] { "t1.png", "t2.png", "t3.png", "t4.png", "t5.png", "t6.png" }, "tiled.png", 2);
                System.out.println("done");
        }
}
6 голосов
/ 13 октября 2010

Я не вижу, как это было бы возможно "без обработки и перекодировки". Если вы настаиваете на использовании Java, тогда я просто предлагаю вам использовать JAI (страница проекта здесь ). При этом вы создадите один большой BufferedImage , загрузите меньшие изображения и начертите их на большем .

Или просто используйте ImageMagick montage:

montage *.png output.png

Для получения дополнительной информации о montage см. использование .

3 голосов
/ 14 октября 2010

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

Если вы собираетесь использовать Java, ваш лучший выбор - при условии, что у вас недостаточно памяти, чтобы вы не могли прочитать весь набор данных в память несколько раз, а затем записать его снова, - это реализовать RenderedImage с классом, который будет читать ваши PNG с диска по требованию. Если вы просто создадите свой новый BufferedImage, а затем попытаетесь записать его, программа записи PNG создаст дополнительную копию данных. Если вы создаете свой собственный RenderedImage, вы можете передать его ImageIO.write(myImageSet,"png",myFileName). Вы можете скопировать SampleModel и ColorModel информацию из вашего первого PNG - надеюсь, они все одинаковые.

Если вы делаете вид, что все изображение состоит из нескольких плиток (по одной плитке на исходное изображение), то ImageIO.write создаст WritableRaster, равный размеру всего набора данных изображения, и вызовет вашу реализацию RenderedImage.copyData заполнить его данными. Если у вас достаточно памяти, это простой способ (потому что вы получаете огромный целевой набор данных и можете просто сбросить в него все данные вашего изображения - используя метод setRect(dx,dy,Raster)) - и тогда вам не нужно переживай еще раз). Я не проверял, экономит ли это память, но мне кажется, что так и должно быть.

В качестве альтернативы, если вы притворяетесь, что все изображение представляет собой одну плитку, ImageIO.write затем, используя getTile(0,0), запросит растр, соответствующий этому целому изображению. Таким образом, вы должны создать свой собственный растр, который, в свою очередь, заставляет вас создавать свой собственный DataBuffer. Когда я попробовал этот подход, минимальное использование памяти, которое успешно записало 15360x25600 RGB PNG, было -Xmx1700M (в Scala, между прочим), что составляет чуть более 4 байтов на пиксель записанного изображения, поэтому очень мало служебной информации над одним полным изображением в памяти.

Сам по себе формат данных PNG не требует полного изображения в памяти - он будет нормально работать в блоках - но, к сожалению, реализация по умолчанию для устройства записи PNG предполагает, что в памяти будет весь массив пикселей.

3 голосов
/ 13 октября 2010

Формат PNG не поддерживает разбиение на листы, поэтому невозможно избежать хотя бы распаковки и повторного сжатия потока данных.Если палитры всех изображений идентичны (или все отсутствуют), это единственное, что вам действительно нужно сделать.(Я также предполагаю, что изображения не чересстрочные.)

Вы можете сделать это потоковым способом, открыв только одну «строку» PNG за раз, считывая фрагменты соответствующего размера из их данных.поток и запись их в выходной поток.Таким образом, вам не нужно хранить целые изображения в памяти.Наиболее эффективным способом было бы запрограммировать это поверх libpng самостоятельно.Вам может потребоваться сохранить в памяти чуть более одной строки развертки пикселей из-за предсказания пикселей.

Но простое использование утилит командной строки ImageMagick, netpbm или аналогичных сэкономит вам большое времячто может быть мало выгоды.

2 голосов
/ 06 июля 2015

Расчесывание изображений

private static void combineALLImages(String screenNames, int screens) throws IOException, InterruptedException {
    System.out.println("screenNames --> D:\\screenshots\\screen   screens --> 0,1,2 to 10/..");
    int rows = screens + 1;
    int cols = 1;
    int chunks = rows * cols ; 

     File[] imgFiles = new File[chunks];
    String files = "";
    for (int i = 0; i < chunks; i++) {
        files = screenNames + i + ".jpg";
        imgFiles[i] = new File(files);          
        System.out.println(screenNames + i + ".jpg"+"\t Screens : "+screens);    

    }

    BufferedImage sample = ImageIO.read(imgFiles[0]);
    //Initializing the final image
    BufferedImage finalImg = new BufferedImage(sample.getWidth() * cols, sample.getHeight() * rows, sample.getType());

    int index = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            BufferedImage temp = ImageIO.read(imgFiles[index]);
            finalImg.createGraphics().drawImage(temp, sample.getWidth() * j, sample.getHeight() * i, null);
            System.out.println(screenNames + index + ".jpg");
            index++;
        }
    }
    File final_Image = new File("D:\\Screenshots\\FinalImage.jpg");
    ImageIO.write(finalImg, "jpeg", final_Image);

}
1 голос
/ 28 мая 2013

Используйте монтаж imagemagick следующим образом:

montage *.png montage.png

Более подробную информацию о параметрах можно найти здесь

Удачи

1 голос
/ 29 ноября 2010

простой скрипт на Python для объединения плиток в одно большое изображение:

import Image

TILESIZE = 256
ZOOM = 15
def merge_images( xmin, xmax, ymin, ymax, output) :
    out = Image.new( 'RGB', ((xmax-xmin+1) * TILESIZE, (ymax-ymin+1) * TILESIZE) ) 

    imx = 0;
    for x in range(xmin, xmax+1) :
        imy = 0
        for y in range(ymin, ymax+1) :
            tile = Image.open( "%s_%s_%s.png" % (ZOOM, x, y) )
            out.paste( tile, (imx, imy) )
            imy += TILESIZE
        imx += TILESIZE

    out.save( output )

run:

merge_images(18188, 18207, 11097, 11111, "output.png")

работает, например, для файлов с именами, такими как% ZOOM_% XCORD_% YCORD.png15_18188_11097.png

1 голос
/ 13 октября 2010

Возможно, вам лучше отскочить от другого (без потерь) формата изображения. PPM очень прост в использовании (и для помещения плиток программно; это просто большой массив на диске, поэтому вам нужно будет хранить не более одного ряда плиток), но это очень расточительно(12 байт на пиксель!).

Затем используйте стандартный конвертер (например, ppm2png), который принимает промежуточный формат и превращает его в гигантский PNG.

...